> For the complete documentation index, see [llms.txt](/llms.txt)

# 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

```swift
// 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")
```

```swift
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](/docs/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](/docs/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](/docs/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](/docs/retries).

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

## 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](/docs/streaming)
and [Authentication](/docs/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.

```swift
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](/docs/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:

```yaml
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`:

```yaml
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**:

```yaml
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.
