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-compileCI 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: truerequirestargets.swift.keychain: trueand is compile-verified on the scheduledmobile-compilelane. 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-compileCI 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.