Skip to content Documentation index for agents (llms.txt)
Glotto Beta
Get started

Swift

The Swift SDK is an idiomatic package emitted from the same GlottoIR as every other target. Operations under a declared resource hang off an accessor — client.pets.listPets() — while an operation you leave ungrouped stays a method on Client. Request/response shapes are Codable Swift types, so you work with real types rather than Data.

Quickstart

// In your Package.swift, add the generated package to `dependencies`
// (a local `.package(path:)` or your own published URL), then its library product:
.product(name: "Petstore", package: "Petstore")
import Foundation
import Petstore

let client = Client(
    baseURL: "https://api.petstore.example",
    token: ProcessInfo.processInfo.environment["PETSTORE_TOKEN"]!
)

// a single call
let pet = try await client.pets.createPet(body: NewPet(name: "Rex"))

// For an operation configured with pagination:
let pets = client.pets.listPetsScoped()
defer { pets.close() }
for try await pet in pets {
    print(pet.name)
}

Typed models

Each GlottoIR model becomes a Codable struct; operations decode and return the typed response and accept a typed request body. Discriminated unions resolve to the right enum case.

Typed errors

Operations throw an APIError struct carrying the parsed error body, with an APIErrorKind enum so callers switch error.kind / if case .notFound rather than matching status codes. See Errors.

Pagination

Scoped companions return a StreamScope that fetches another page only when you advance beyond the current page. Put close() in a defer block to release it when your code breaks, returns, or throws. Existing AsyncThrowingStream methods remain available; cancel their consuming task when leaving early. See Pagination.

Manual Page companions expose typed items and explicit nextPage() navigation. The throwing response() accessor decodes the full wrapper without another request. See manual pagination.

Retries & backoff

Pass RequestOptions to override headers, timeouts, retry counts and idempotency for an individual operation. Method defaults apply before caller overrides. See request options and defaults.

Transient failures (5xx, 429, transport errors) retry with exponential backoff and jitter, configurable per client. See Retries & timeouts.

SSE, NDJSON, and WebSocket streaming

SSE and NDJSON Scoped companions provide the same StreamScope ownership pattern as pagination. The opening deadline and retry policy apply until the first response byte. After that boundary, streams outlive the opening timeout and read failures surface without replay.

WebSocket operations retain their typed connection API. Put connection.close() in defer when leaving a message loop early. The upgrade uses the configured client, and a successful 101 ends the opening deadline before the first application frame arrives. See Streaming and Authentication.

Linux transport requirements

SDKs with binary downloads, WebSocket operations, GraphQL subscriptions or event channels require Swift 6.1 or newer and the system zlib development library on Linux (zlib1g-dev on Debian/Ubuntu). Their Linux transport uses SwiftNIO and NIOSSL; Apple defaults use Foundation. SDKs without these capabilities retain their Swift 6.0 requirement.

For Client binary-download and WebSocket methods, supplying session: on Linux also requires an explicit streamingTransport:. Choose NIOStreamingTransport() or implement StreamingTransport for custom policy. The built-in transport verifies TLS certificates and hostnames and accepts optional PEM trust roots and a client certificate/key; it does not follow redirects or supply cookie storage, caching, proxies or authentication-challenge handling. A supplied URLSession continues to serve ordinary requests, and its policy is not copied into the streaming transport. GraphQLTransport and EventTransport remain the custom-transport interfaces for their respective clients. The generated README includes the applicable configuration example.

Unknown response fields

Your API can add a response field without it being a breaking change — but a generated Codable struct decodes only its declared CodingKeys, so the key would be dropped. The generated models keep it instead: a field the SDK wasn’t generated from is retained on decode, readable through an accessor, and written back out when the model is re-serialized.

let pet = try await client.pets.createPet(body: body)

// A field your API started returning after this SDK was generated.
if let species = pet.extraFields["species"] {
    print(species)
}

// Re-encoding preserves it — a read-modify-write never silently drops it.
let json = try JSONEncoder().encode(pet)

extraFields is a let holding [String: JSONValue], so nested objects and arrays survive intact, and retention is recursive.

Your existing construction still compiles: the memberwise initializer keeps its original signature and starts the model with an empty bag.

The retained fields are read-only by design. To send a field your spec doesn’t model yet, use the per-call extra-body escape hatch rather than writing to the retained bag.

Binary downloads

Binary endpoints return an owned BinaryResponse with response metadata. readAll(maxBytes:) bounds delivered bytes; copy(to:) accepts a FileHandle or async sink closure. Use defer { download.close() } after opening an incremental reader. Mixed JSON/binary operations expose an explicit result branch. See file transfers.

File uploads

multipart/form-data operations build the multipart body from their fields for you; an application/octet-stream operation takes a positional body: Data argument sent raw with the right Content-Type.

Keychain secure storage

Opt-in. The Keychain target is compile-verified against a real Apple toolchain (macOS) by the scheduled mobile-compile CI lane — the #if canImport(Security) Keychain store builds on a real Apple SDK. The Swift SDK above is unaffected.

Opt in with targets.swift.keychain: true in your glotto.yml to add a pluggable secure token store to the bearer client — the same native-mobile secure-storage seam the React Native SDK ships:

targets:
  swift:
    keychain: true

The client gains an optional tokenStore: SecureTokenStore? parameter; when supplied, the bearer token is read from it per request (falling back to the static token). A Keychain-backed implementation (KeychainTokenStore, over the Security framework SecItem generic-password API) is generated for you. SecureTokenStore is a plain protocol, so you can supply your own store too. The Keychain implementation is #if canImport(Security)-guarded, so the SDK still compiles on Linux (where the Security framework is absent) — exactly like the engine’s streaming Linux fallbacks.

Biometric-gated storage

Opt-in, experimental. targets.swift.keychainBiometric: true requires targets.swift.keychain: true and is compile-verified on the scheduled mobile-compile lane. The plain Keychain store above stays the default and byte-identical.

For credentials that must be released only after the user authenticates, add targets.swift.keychainBiometric: true:

targets:
  swift:
    keychain: true
    keychainBiometric: true

This additionally emits a BiometricKeychainTokenStore (alongside the plain KeychainTokenStore) whose Keychain item carries a SecAccessControlCreateWithFlags(.biometryCurrentSet) access control, so the OS presents the biometric prompt on access. An optional LAContext (LocalAuthentication-guarded) lets you reuse an existing authentication or customize the prompt. It rides the same #if canImport(Security) guard, so the SDK still compiles on Linux.

Connectivity-aware retries & lifecycle-aware backoff

Opt-in. The concrete monitors are compile-verified against a real Apple toolchain (macOS) by the scheduled mobile-compile CI lane — the #if canImport(Network) / #if canImport(UIKit) monitors build on a real Apple SDK. The Swift SDK above is unaffected.

The retry loop can be made network- and lifecycle-aware — the two remaining native-mobile seams the React Native and Kotlin-Android SDKs ship. They are two independent opt-in flags:

targets:
  swift:
    connectivity: true   # pause retries while the device is offline
    lifecycle: true      # pause backoff while the app is backgrounded

With connectivity on, the Client gains an optional connectivity: ConnectivityMonitor? parameter. Before each (re)try, a known-offline state is treated as transient — the client waits and re-checks rather than burning the request. A concrete NWPathMonitorConnectivity (over the Network framework) is generated for you.

With lifecycle on, the Client gains an optional lifecycle: AppLifecycle? parameter. While the app is backgrounded (where the OS freezes timers), the client pauses the backoff without consuming a retry attempt until the app returns to the foreground. A concrete UIApplicationLifecycle (over UIKit lifecycle notifications) is generated for you.

ConnectivityMonitor and AppLifecycle are plain protocols, so you can supply your own implementations too. Both concrete monitors are #if canImport(...)-guarded (Network / UIKit), so the SDK still compiles on Linux where those frameworks are absent — exactly like the Keychain store above.