Documentation
Documentation · iOS
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 and Apple Team ID. For simulator testing, set GATE_AI_DEV_TOKEN in the Xcode Run scheme environment. Requires iOS 16+ and Xcode 16+.
  4. Use `GateAIClient` for requests. Call `performProxyRequest` or `authorizationHeaders` so every proxied API call carries DPoP proofs.

Configure the client

import GateAI

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

// Simulator builds pick up a development token from the
// GATE_AI_DEV_TOKEN environment variable (Edit Scheme → Run →
// Arguments → Environment Variables). Device builds ignore it.

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
    }
}

Set a simulator development token in Xcode

App Attest is unavailable in the iOS Simulator. Set GATE_AI_DEV_TOKEN as a runtime environment variable. The SDK reads it automatically; your client configuration stays the same.

  1. Create a development token for your gate in the Gate/AI portal and copy its value.
  2. In Xcode, select your app’s scheme. Keep the token in a personal, unshared scheme: choose Product → Scheme → Manage Schemes… and use an unshared copy of the app scheme. Keep its xcuserdata files out of source control.
  3. Open Product → Scheme → Edit Scheme… or press ⌘⇧, (Command–Shift–Comma on a US keyboard; shown as ⌘< in the menu).
  4. Select Run in the left sidebar, then open the Arguments tab.
  5. Under Environment Variables, click +. Set the name to GATE_AI_DEV_TOKEN and paste your token into the Value column. Leave the checkbox beside the variable enabled.
  6. Click Close, select an iOS Simulator, and run the app from Xcode with ⌘R. Stop and rerun the app after changing the token.

These steps also apply to the iOS host app for React Native, Capacitor, and Flutter. Scheme variables are passed when Xcode launches the app; a launch from another tool must provide the variable to the app process separately.

Physical devices ignore this variable and use App Attest. Tokens expire according to the expiration chosen in the portal; revoke unused tokens there.

Xcode scheme and environment variable reference →

Analytics headers

The SDK automatically includes analytics headers on all authenticated requests. They power the per-gate analytics in this portal and are stripped before your request is forwarded to the AI provider.

Automatic headers

  • X-Client-Locale User's language and region (e.g., "en-US", "es-MX")
  • X-App-Version App version from the bundle (CFBundleShortVersionString)
  • X-OS-Version iOS version (e.g., "17.2")
  • X-Device-Identifier Vendor identifier (identifierForVendor UUID)
  • X-Device-Type Device class (e.g., "iPhone", "iPad")
  • X-Device-Model Hardware model identifier (e.g., "iPhone16,1")
  • X-Environment "development", "testflight", or "production" — detected from the build
  • X-SDK-Version Gate/AI SDK version

Developer-set headers

Optional properties on the client; only sent when you set them.

// X-User-Status: user segment or subscription tier
gateAIClient.userStatus = "premium"

// X-User-Identifier: opaque account ID from your
// own system — never an email or name
gateAIClient.userIdentifier = account.analyticsId

// X-App-Feature: which in-app feature is making
// AI requests, for per-feature cost attribution
gateAIClient.appFeature = "chat"

// X-User-Tier: the user's plan tier — per-tier usage
// limits configured for this gate match it exactly
gateAIClient.userTier = "pro"

Country-level geography is derived server-side from the request's network edge — nothing to configure and no location permission involved.

Usage limits & quotas

Gates can enforce per-device budgets over daily, calendar-month, rolling 30-day, and billing-cycle windows. The SDK reports the user's renewal day and surfaces remaining quota so you can render limit UI, and per-tier limits key off the client's userTier value — and make hitting a cap your upgrade moment.

Billing-cycle windows

To align a device's budget with the user's subscription month, report their renewal day-of-month (from StoreKit or RevenueCat). Sent as the X-Quota-Anchor-Day header; days 29–31 clamp to short months automatically. Requires SDK 1.1.0+.

// Day-of-month (1–31) the user's subscription renews
gateAIClient.quotaAnchorDay = renewalDayOfMonth

Reading remaining quota

Responses include the remaining budget for the tightest configured window:

  • X-Quota-Requests-Remaining with X-Quota-Requests-Limit for rendering usage meters
  • X-Quota-Requests-Reset ISO8601 date the request budget resets
  • X-Quota-Tokens-Remaining with X-Quota-Tokens-Limit
  • X-Quota-Tokens-Reset ISO8601 date the token budget resets

When a limit is hit, the 429 body names the window and reset date so your app can show "resets in N days":

{ "error": "rate_limited",
  "code": "device_monthly_requests_exceeded",
  "window": "monthly", "limit": 200, "used": 200,
  "resets_at": "2026-10-01T00:00:00.000Z" }

Resources