iOS implementation

Gate/AI for iOS

Integrate the `GateAI` Swift package to manage Secure Enclave keys, App Attest, short-lived access tokens, and DPoP proofs for every proxied API call.

Quick start checklist

  1. Add the Swift package. In Xcode ▸ File ▸ Add Packages… select the local `gate-ios` directory or hosted repository.
  2. Enable App Attest. Confirm the entitlement is active for your bundle identifier and Apple Team ID.
  3. Configure `GateAIConfiguration`. Provide the Gate/AI base URL, Team ID, optional bundle override, and a simulator-only dev token.
  4. Use `GateAIClient` for requests. Call `performProxyRequest` or `authorizationHeaders` so every proxied API call carries DPoP proofs.

Configure the client

import GateAI

#if targetEnvironment(simulator)
let devToken = ProcessInfo.processInfo.environment["GATEAI_DEV_TOKEN"]
#else
let devToken: String? = nil
#endif

let configuration = try GateAIConfiguration(
    baseURLString: "https://your-team.us01.gate-ai.net",
    teamIdentifier: "ABCDE12345",
    developmentToken: devToken,
    logLevel: .info
)

let gateAIClient = GateAIClient(configuration: configuration)
gateAIClient.userStatus = "premium"

Make proxied requests

struct ChatRequest: Encodable {
    let model: String
    let messages: [Message]
}

struct Message: Encodable {
    let role: String
    let content: String
}

let request = ChatRequest(
    model: "gpt-4o",
    messages: [
        Message(role: "user", content: "Say hello from Gate/AI")
    ]
)

let body = try JSONEncoder().encode(request)

let (data, response) = try await gateAIClient.performProxyRequest(
    path: "openai/chat/completions",
    method: .post,
    body: body,
    additionalHeaders: ["Content-Type": "application/json"]
)

guard response.statusCode == 200 else {
    throw MyAppError.unexpectedStatus(response.statusCode)
}

Manual URLRequest integration

Use a custom networking stack? Pull headers from the client and handle nonce challenges manually.

var request = URLRequest(url: configuration.baseURL.appendingPathComponent("anthropic/v1/messages"))
request.httpMethod = "POST"
request.httpBody = anthropicPayload
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("2023-06-01", forHTTPHeaderField: "anthropic-version")

do {
    let headers = try await gateAIClient.authorizationHeaders(
        for: request.url!,
        method: .post
    )

    headers.forEach { key, value in
        request.setValue(value, forHTTPHeaderField: key)
    }

    let (data, response) = try await URLSession.shared.data(for: request)
    // Handle response...
} catch {
    if let nonce = gateAIClient.extractDPoPNonce(from: error) {
        let retryHeaders = try await gateAIClient.authorizationHeaders(
            for: request.url!,
            method: .post,
            nonce: nonce
        )

        retryHeaders.forEach { key, value in
            request.setValue(value, forHTTPHeaderField: key)
        }

        _ = try await URLSession.shared.data(for: request)
    } else {
        throw error
    }
}

Simulator & CI tips

Simulator configuration

#if targetEnvironment(simulator)
let configuration = try GateAIConfiguration(
    baseURLString: "https://your-team-staging.us01.gate-ai.net",
    teamIdentifier: "ABCDE12345",
    developmentToken: Secrets.gateAIDevToken
)
#else
let configuration = try GateAIConfiguration(
    baseURLString: "https://your-team.us01.gate-ai.net",
    teamIdentifier: "ABCDE12345"
)
#endif

Dev token expectations

  • Valid only in staging tenants; production enforces attestation.
  • Scope must include `dev:token` with ≤24h expiry.
  • DPoP replay protection and denylist rules still apply.
  • Responses include `mode: "dev"` for feature gating.

Resources