# Authentication | Swift AI Autocomplete Docs

> For the complete documentation index, see [llms.txt](https://ai-autocomplete.com/llms.txt). A markdown version of any page on this site is available by appending `.md` to its URL path — the homepage is at `/index.md`.

Authentication guide for integrating the AI Autocomplete SDK with Swift. Code examples, setup, and API reference.

Source: https://ai-autocomplete.com/docs/swift/authentication

---

Two key types. Public keys plug straight into the SDK from the app. Secret keys live on your server and mint short-lived access tokens that the SDK uses — recommended for production.

[Manage your keys](https://ai-autocomplete.com/account/keys)

## Choosing a mode

Public key mode is the fastest path: drop a pk_v1_ key into apiConfig and you're done. Secret key + access token mode is what we recommend for production — your server keeps the secret key, mints short-lived access tokens, and the SDK handles refresh transparently.

## Public key mode

Use a public key when calling the API straight from the app. Construct .apiKey with the key; everything else is optional.

**PublicKeyAuth.swift**

```swift
AIAutocomplete(configuration: .init(
    apiConfig: .apiKey(.init(
        // Public key (pk_v1_...) — safe to ship in the app binary
        apiKey: "pk_v1_your_public_key",
        appIdentifier: "com.example.myapp",
        extraHeaders: ["X-Tenant": "acme"]
    )),
    onSubmit: { result in print(result.query) }
))
```

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `apiKey` Required | `String` | — | Your public key (pk_v1_...). Sent as Authorization: Bearer. |
| `endpoint` | `URL?` | `"https://api.ai-autocomplete.com/api/suggest"` | Suggest endpoint override; nil uses the default. |
| `authScheme` | `.bearer \| .basic` | `.bearer` | Send the key as a Bearer token (default) or Basic credentials. |
| `appIdentifier` | `String?` | — | Echoed in X-App-Identifier for tenant routing and telemetry segmentation. |
| `extraHeaders` | `[String: String]` | `[:]` | Merged into every request. Authorization is always overwritten by the SDK. |

Security

Public keys (pk_v1_...) are designed to ship in app binaries. Secret keys (sk_v1_...) are not — never embed one; strings in an IPA are extractable.

## Secret key + access token mode

Your backend exchanges the secret key for a short-lived access token (at_v1_...); the app fetches that token from your backend and hands it to the SDK. The secret key never leaves your server.

### On your server

On your backend, expose a route that exchanges your secret key for a short-lived access token by calling the endpoint below. Return the response to the app; the SDK reads it through getAccessToken. The secret key must never reach the app — keep it in a server-side environment variable, never embedded in the app binary.

POST `https://api.ai-autocomplete.com/api/auth/token`

Headers

`Authorization: Bearer <sk_v1_…>` — your secret key.

Body

`product_id` — the UUID of the product the token is for. Visible in the URL of the product editor at /edit.

Request body

```json
{
  "product_id": "<uuid>"
}
```

200 OK

```json
{
  "access_token": "at_v1_AbCdEfGh...",
  "expires_at": 1775464738,
  "token_type": "Bearer"
}
```

expires_at is unix seconds. The Swift SDK's AccessToken.expiresAt expects unix milliseconds — multiply by 1000 (see below).

401 Unauthorized

```json
{
  "error": {
    "code": "INVALID_SECRET_KEY",
    "message": "The provided secret key is invalid"
  }
}
```

### In the app

getAccessToken calls a route on your own backend, decodes the token + expiry, and returns an AccessToken. The SDK caches it and refreshes 30 seconds before expiry. No secret-key code lives in the app.

**AccessTokenAuth.swift**

```swift
// IN-APP ONLY. No secret key here — only the URL of your own
// backend route, which holds the secret key.
struct TokenResponse: Decodable {
    let accessToken: String
    let expiresAt: Double // unix seconds from your backend
}

AIAutocomplete(configuration: .init(
    apiConfig: .accessToken(.init(
        getAccessToken: {
            let url = URL(string: "https://your-backend.example.com/api/ac-token")!
            let (data, _) = try await URLSession.shared.data(from: url)

            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            let payload = try decoder.decode(TokenResponse.self, from: data)

            // The Swift SDK expects expiresAt in unix MILLISECONDS.
            return AccessToken(
                accessToken: payload.accessToken,
                expiresAt: payload.expiresAt * 1000
            )
        }
    )),
    onSubmit: { result in print(result.query) }
))
```

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `getAccessToken` Required | `() async throws -> AccessToken` | — | Async token producer. Concurrent callers are coalesced onto one in-flight fetch. |
| `accessToken` | `String?` | — | Pre-seeded token — skips the cold-start fetch when you already hold a fresh one. |
| `endpoint` | `URL?` | `"https://api.ai-autocomplete.com/api/suggest"` | Suggest endpoint override; nil uses the default. |
| `appIdentifier` | `String?` | — | Echoed in X-App-Identifier. |
| `extraHeaders` | `[String: String]` | `[:]` | Merged into every request. Authorization is always overwritten by the SDK. |

Seconds vs. milliseconds

The token endpoint returns expires_at in unix seconds; AccessToken.expiresAt is unix milliseconds. Multiply by 1000 when bridging — a seconds value reads as long-expired and forces a refresh on every request. Pass nil to disable proactive refresh entirely (the SDK then refreshes only on a 401).

How refresh works

The SDK refreshes 30 seconds before expiresAt, coalescing concurrent callers onto one in-flight fetch, and retries once with a forced refresh on a 401. If the retry also gets a 401, onError receives .unauthorized.
