Browse documentation

Swift / iOS SDK

The official nRouter Swift SDK for iOS, macOS, watchOS, and visionOS with Swift Package Manager support, async/await, and zero third-party dependencies.

Last updated

The official nRouter Swift SDK (NRouter) provides zero-dependency, idiomatic Swift bindings for Apple platforms including iOS 15+, macOS 12+, tvOS 15+, watchOS 8+, and visionOS 1+. Built natively on URLSession and modern Swift async/await structured concurrency, it connects directly to https://api.nrouter.ai/v1 with automated response metadata parsing, Server-Sent Events (SSE) streaming, and real-time cost attribution.

By communicating through nRouter, native Apple applications gain centralized access to major AI models (OpenAI, Anthropic, Google Gemini, and open-source models) through a unified client, with server-side prompt injection defense and organization guardrails applied before tokens reach downstream providers.

Prerequisites & Installation

The SDK requires Swift 5.9+ and Xcode 15+. It introduces zero third-party dependencies, preserving lean app binary sizes.

Swift Package Manager (Package.swift)

Add the package dependency to your Package.swift:

dependencies: [
    .package(url: "https://github.com/nRouterGateway/nrouter-sdk.git", from: "2.2.1")
]

Then add NRouter to your executable or framework target:

.target(
    name: "YourApp",
    dependencies: [
        .product(name: "NRouter", package: "nrouter-sdk")
    ]
)

Xcode GUI Installation

  1. Open your project in Xcode.
  2. Navigate to File → Add Package Dependencies...
  3. In the search field, enter: https://github.com/nRouterGateway/nrouter-sdk.git
  4. Select the Dependency Rule: Up to Next Major from 2.2.1.
  5. Check NRouter and add it to your target application.

Setup & Configuration

Configure authentication based on your deployment environment.

Environment Variable (macOS & Server-Side Swift)

For command-line tools, background daemons, or server-side Swift (Vapor), export your key:

export NROUTER_API_KEY="sk-nrouter-xxxxxxxxxxxx"

Initialize the client with automatic environment resolution:

import NRouter

// Automatically reads NROUTER_API_KEY and targets https://api.nrouter.ai/v1
let client = try NRouter()

Direct Initialization & iOS Security

On iOS, watchOS, and visionOS, shell environment variables are unavailable. Pass your virtual key directly:

import NRouter

let client = try NRouter(
    apiKey: "sk-nrouter-xxxxxxxxxxxx",
    baseUrl: "https://api.nrouter.ai/v1"
)

Security Invariant: Never bundle permanent administrative API keys inside production iOS or macOS binaries. Mobile binaries can be decompiled and network traffic inspected. Always mint short-lived, budget-capped virtual keys from your backend authentication service and pass them to client devices.

Configuration Parameters

ParameterTypeDefaultDescription
apiKeyStringEnvironmentnRouter virtual key (sk-nrouter-...).
baseUrlStringhttps://api.nrouter.ai/v1Unified gateway base URL. Must include /v1.
timeoutTimeInterval60.0Total request timeout in seconds for URLSessionConfiguration.
defaultHeaders[String: String][:]Custom headers attached to every call (e.g. x-client-name).
import Foundation
import NRouter

// Advanced configuration with custom timeout and a default header
let client = try NRouter(
    apiKey: "sk-nrouter-xxxxxxxxxxxx",
    baseUrl: "https://api.nrouter.ai/v1",
    timeout: 30.0,
    defaultHeaders: [
        "x-client-name": "checkout-service"
    ]
)

Implementation Patterns

1. Chat Completion

Execute a synchronous completion and unpack the resulting JSON payload:

import NRouter

let client = try NRouter()

let payload: [String: Any] = [
    "model": "gpt-5.4-mini",
    "messages": [
        ["role": "system", "content": "You are a concise iOS engineer."],
        ["role": "user", "content": "Explain Swift structured concurrency."]
    ],
    "max_completion_tokens": 512
]

let result = try await client.chatCompletions(payload)

if let choices = result["choices"] as? [[String: Any]],
   let firstChoice = choices.first,
   let message = firstChoice["message"] as? [String: Any],
   let content = message["content"] as? String {
    print("Response:\n\(content)")
}

2. Real-time Streaming with AsyncThrowingStream

Stream tokens directly to your SwiftUI views as they arrive:

import SwiftUI
import NRouter

@MainActor
class ChatViewModel: ObservableObject {
    @Published var streamedText = ""
    @Published var isGenerating = false

    private let client = try? NRouter()

    func streamCompletion(prompt: String) async {
        guard let client = client else { return }
        isGenerating = true
        streamedText = ""

        let payload: [String: Any] = [
            "model": "claude-haiku-4-5-20251001",
            "messages": [["role": "user", "content": prompt]],
            "stream": true
        ]

        do {
            let stream = try await client.chatCompletionsStream(payload)
            for try await chunk in stream {
                if let delta = chunk.deltaContent {
                    self.streamedText += delta
                }
            }
        } catch {
            print("Stream error: \(error.localizedDescription)")
        }

        isGenerating = false
    }
}

3. Per-Request Gateway Overrides

Attach template IDs or cache preferences within the request dictionary:

let payload: [String: Any] = [
    "model": "gpt-5.5",
    "messages": [["role": "user", "content": "Analyze quarterly report"]],
    "nrouter_prompt_template_id": "tmpl_financial_brief_v1",
    "nrouter_prompt_variables": ["currency": "USD"],
    "nrouter_cache": true
]

let response = try await client.chatCompletions(payload)

Production Best Practices

Deterministic Routing & Fallbacks

There are two ways to ask for a model. Direct — you choose the model. nRouter never swaps it for another; a different model answers only through a fallback you configured yourself, and only when the provider cannot take the request. nrouter/auto — nRouter chooses an eligible model for you. The model string you send selects the way:

let payload: [String: Any] = [
    // nRouter chooses an eligible model; x-nr-model names the one that answered
    "model": "nrouter/auto",
    "messages": [["role": "user", "content": "Verify receipt image"]]
]
  • The nrouter/auto preference is an organization setting. It is chosen on the nrouter/auto tab of Router Settings: Best for the task (default) uses smart model selection when it is available; Lowest cost orders the models fit for the task by their list rate. No request header selects either way. Either way you pay the served model's list price.
  • Fallbacks for one call. On a directly named model, put nrouter_fallbacks (one to four model names) in the JSON request body. See Per-Request Options.
  • When another model is tried. Only when the provider cannot take the request: an upstream 429, 503 or Anthropic 529, a 401 or 404 from a misconfigured provider deployment, a connection that never opened, or a provider that sends no first byte in time. A 500, 502, 504 or a read timeout after the connection opened is returned to the caller.
  • What the response tells you. x-nr-routing is a response header: direct, or fallback:<n> when a later entry answered. x-nr-model names the model that answered.

Latency Tuning on Apple Platforms

  1. Pre-warmed URLSession: Initialize NRouter during application launch to pre-warm DNS resolution and TLS handshakes before user actions.
  2. Keep-Alive Connections: Default URLSession configurations maintain persistent HTTP/2 connections to reduce connection latency.
  3. Cancel Unread Streams: In SwiftUI views, tie tasks to view lifecycles (.task(id: ...)). When the view disappears, Swift task cancellation aborts the underlying network stream immediately, preventing wasted tokens.

Response Telemetry & Spend Tracking

Inspect gateway metadata on completions:

if let meta = result["_meta"] as? [String: Any] {
    let requestId = meta["request_id"] as? String ?? "unknown"
    let cost = meta["cost"] as? Double ?? 0.0
    let model = meta["model"] as? String ?? "unknown"
    print("Served by: \(model) | ID: \(requestId) | Cost: $\(cost)")
}

Troubleshooting & Error Handling

The Swift SDK surfaces errors through the NRouterError type.

Common Error Codes

StatusCodeCauseRecommended Action
400guardrail_blockedInput rejected by server-side content or injection guardrailsVerify prompt safety; review guardrail settings in nRouter dashboard.
401invalid_api_keyMissing, incorrect, or expired virtual API keyRe-authenticate client; mint a fresh virtual key from your backend.
402insufficient_creditsNot enough credits, or a budget ceiling was hitAdd funds in dashboard or update key ceiling.
429rate_limit_exceededClient exceeded RPM/TPM quotaImplement backoff retry logic.
500 / 503service_unavailableDownstream provider error or network disruptionSave a fallback list for the model in Router Settings or send nrouter_fallbacks: another model is tried on 503, not on 500.

Error Catching Pattern

import NRouter

do {
    let response = try await client.chatCompletions(payload)
    print("Success: \(response)")
} catch let error as NRouterError {
    switch error {
    case .guardrailBlocked(let reason):
        print("Blocked by security policy: \(reason)")
    case .authenticationFailed:
        print("Invalid virtual key: Please re-authenticate.")
    case .insufficientCredits:
        print("Account budget ceiling reached.")
    case .rateLimited(let retryAfter):
        print("Rate limited: Retry after \(retryAfter ?? 1)s.")
    case .serverError(let status, let msg):
        print("Gateway error [\(status)]: \(msg)")
    case .networkError(let underlying):
        print("Network transport error: \(underlying.localizedDescription)")
    }
} catch {
    print("Unexpected error: \(error)")
}

Next Steps

Was this page helpful?