Documentation · iOS
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
- Add the Swift package. In Xcode ▸ File ▸ Add Packages… select the local `gate-ios` directory or hosted repository.
- Enable App Attest. Confirm the entitlement is active for your bundle identifier and Apple Team ID.
- 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+.
- 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.
- Create a development token for your gate in the Gate/AI portal and copy its value.
- 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.
- Open Product → Scheme → Edit Scheme… or press ⌘⇧, (Command–Shift–Comma on a US keyboard; shown as ⌘< in the menu).
- Select Run in the left sidebar, then open the Arguments tab.
-
Under
Environment Variables,
click
+.
Set the name to
GATE_AI_DEV_TOKENand paste your token into the Value column. Leave the checkbox beside the variable enabled. - 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.
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-LocaleUser's language and region (e.g., "en-US", "es-MX") -
X-App-VersionApp version from the bundle (CFBundleShortVersionString) -
X-OS-VersioniOS version (e.g., "17.2") -
X-Device-IdentifierVendor identifier (identifierForVendor UUID) -
X-Device-TypeDevice class (e.g., "iPhone", "iPad") -
X-Device-ModelHardware model identifier (e.g., "iPhone16,1") -
X-Environment"development", "testflight", or "production" — detected from the build -
X-SDK-VersionGate/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-RemainingwithX-Quota-Requests-Limitfor rendering usage meters -
X-Quota-Requests-ResetISO8601 date the request budget resets -
X-Quota-Tokens-RemainingwithX-Quota-Tokens-Limit -
X-Quota-Tokens-ResetISO8601 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" }