# Quickstart

This page gets AppAttest running in an existing iOS app. End-to-end, it takes about five minutes on a real device. The rest of the docs go deeper on each step.

## Before you start

- An iOS app you can run on a real device. App Attest does not run in the simulator without a debug stub (see the SDK reference).
- Apple Developer Program membership. App Attest is an Apple capability and requires signed builds.
- An appAttest account. Sign up at <https://app.staging.appattest.dev/signup>.

## 1. Create the app in AppAttest

In the dashboard at <https://app.staging.appattest.dev/dashboard>:

1. Click **New app**.
2. Enter your bundle identifier. Must match your Xcode project exactly. Example: `com.yourcompany.yourapp`.
3. Pick a team. The default team is fine for a solo setup.

The dashboard now has one app with two environments: `development` and `production`.

## 2. Add secrets

In your new app:

1. Select the `development` environment.
2. Click **Add secret**.
3. Give it a name, for example `openai`.
4. Paste the value.

Values are shown once. Copy it now if you need it elsewhere. AppAttest cannot show it again.

Repeat for `production` with the production-grade key when you're ready.

## 3. Turn on App Attest in Apple Developer Console

Full walkthrough: see [Apple Developer Console setup](/docs/apple-developer-console).

Short version:

1. <https://developer.apple.com/account> → **Certificates, Identifiers & Profiles** → **Identifiers**.
2. Open your app identifier.
3. Scroll to **App Services**. Enable **App Attest**.
4. Save.

You don't need to regenerate provisioning profiles for this change.

## 4. Xcode configuration

Full walkthrough: see [Xcode setup](/docs/xcode-setup).

Short version:

1. Open the project.
2. Select your app target.
3. **Signing & Capabilities** → **+ Capability** → **App Attest**.
4. Xcode adds `App Attest` to your entitlements file. That's it.

Reference: [entitlements](/docs/entitlements).

## 5. Add the Swift SDK

In Xcode:

1. **File** → **Add Package Dependencies**.
2. URL: `https://github.com/AppAttest/sdk`.
3. Pick **Up to Next Major Version** from the latest tagged release.
4. Add the `AppAttest` product to your app target.

Or in `Package.swift`:

```swift
dependencies: [
    .package(url: "https://github.com/AppAttest/sdk", from: "0.1.0")
],
targets: [
    .target(
        name: "MyApp",
        dependencies: [
            .product(name: "AppAttest", package: "sdk")
        ]
    )
]
```

Platform floor: iOS 17 / macOS 14 / tvOS 17 / watchOS 10 (locked by the `@Observable` macro).

## 6. Initialize and use

The SDK has no configuration. Bundle ID and Team ID come from the signed app at runtime — there's no key to paste in. One synchronous call in your App init does the whole bootstrap:

```swift
import SwiftUI
import AppAttest

@main
struct MyApp: App {
    init() { AppAttest.start() }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}
```

`AppAttest.start()` is idempotent and returns in microseconds. Internally it hydrates any previously-synced secrets from Keychain (so cold-start reads feel synchronous), spawns the background attest + sync, and registers a foreground observer so the next foreground re-syncs.

Read a secret anywhere in your app via the synchronous subscript:

```swift
struct ContentView: View {
    var body: some View {
        if let key = AppAttest.secrets["OPENAI_API_KEY"] {
            Text("Ready")
        } else {
            ProgressView("Loading…")
        }
    }
}
```

`AppAttest.secrets` is an in-memory dict observed by SwiftUI through `AppAttestClient.shared` (which is `@Observable @MainActor`). When a value lands, any view reading it re-renders. The subscript returns `String?` — nil while the first sync is still running, the value once it lands.

For testability or explicit injection, prefer `@Environment(AppAttestClient.self)`:

```swift
@main
struct MyApp: App {
    init() { AppAttest.start() }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(AppAttestClient.shared)
        }
    }
}

struct ContentView: View {
    @Environment(AppAttestClient.self) private var attest

    var body: some View {
        Text(attest.secrets["OPENAI_API_KEY"] ?? "Loading…")
    }
}
```

If you need a bootstrap step that absolutely cannot run before secrets are present, use the one-time async helper:

```swift
.task {
    try? await AppAttest.waitForReady()
    APIClient.configure(token: AppAttest.secrets["BACKEND_KEY"]!)
}
```

`waitForReady()` throws on `.subscriptionRequired`, `.creditsRequired`, or `.unavailable` — let your error UI react accordingly.

## 7. Observe state for unhappy paths

Errors that aren't recoverable per-call live on `AppAttest.state` rather than thrown from the read path. Switch on it where you want to gate UI:

```swift
struct RootView: View {
    @Environment(AppAttestClient.self) private var attest

    var body: some View {
        switch attest.state {
        case .initializing, .attesting, .syncing:
            SplashView()
        case .ready:
            MainView()
        case .subscriptionRequired(let err):
            UnavailableView(title: "Service paused", error: err)
        case .creditsRequired(let err):
            UnavailableView(title: "Service paused", error: err)
        case .unavailable(let err):
            switch err {
            case .attestationRejected:
                AttestationFailedView(error: err)
            default:
                RetryView(error: err) { attest.retry() }
            }
        }
    }
}
```

`retry()` re-runs the sync without re-attesting — useful for a Retry button on the failure state. `.attestationRejected` is terminal for this install; the other `.unavailable` cases (`.serviceUnavailable`, `.network`) retry automatically on next foreground.

**For end-user-facing apps:** show a generic "service temporarily unavailable" view when state isn't `.ready`. Don't surface our specific reasons or the embedded `subscribeUrl` / `topupUrl` to end users — they're for your developer-mode diagnostics or admin flows.

## 8. Verify

Run on a real device. In the AppAttest dashboard, open your app and watch **Recent activity**. You should see:

- One `attestation.succeeded` event.
- One `sync.succeeded` event with the count of delivered secrets.

If you don't see activity within 30 seconds, see [errors and debugging](/docs/entitlements#troubleshooting).

## What's next

- [Apple Developer Console setup](/docs/apple-developer-console) — the Identifier and App Attest toggle in depth.
- [Xcode setup](/docs/xcode-setup) — capability, entitlements, signing.
- [Entitlements reference](/docs/entitlements) — what goes in the file and why.
- [For AI agents](/docs/agents) — how an agent should read these docs on a developer's behalf.