# Xcode setup

This page covers the Xcode side of setup: adding the App Attest capability, adding the Swift package, and pointing the client at your project. The Apple Developer Console steps come first — see [Apple Developer Console setup](/docs/apple-developer-console).

## 1. Add the App Attest capability

1. Open your project in Xcode.
2. Select the project in the Navigator (top item, blue icon).
3. Select your app target from the **TARGETS** list.
4. Click the **Signing & Capabilities** tab.
5. Click **+ Capability** at the top of the capability list.
6. In the search field, type **App Attest**.
7. Double-click **App Attest** to add it.

Xcode does two things:

- Writes an entry to your app target's entitlements file. If you don't have one yet, Xcode creates `YourAppName.entitlements` next to your source.
- Updates the target's build settings to point at that entitlements file.

See [entitlements](/docs/entitlements) for the exact contents.

## 2. Check signing

In the same **Signing & Capabilities** tab:

- **Automatically manage signing** should be checked unless you have a reason to manage profiles manually.
- The **Team** dropdown should show the team that owns the identifier you enabled App Attest on.
- **Bundle Identifier** should match that identifier exactly.

Build once. Xcode will regenerate the provisioning profile if needed. If you see a signing error, Xcode usually shows a **Try Again** button that fixes it.

## 3. Add the AppAttest Swift package

1. **File** → **Add Package Dependencies**.
2. In the search field at the top right, paste: `https://github.com/AppAttest/sdk`.
3. Wait for Xcode to resolve it.
4. **Dependency Rule**: **Up to Next Major Version**, from the latest tagged release.
5. Click **Add Package**.
6. In the product selection dialog, add the **AppAttest** product to your **app target**. Do not add it to test targets.

Xcode adds the package to your project's Package Dependencies list and links it into the app.

## 4. Wire the SDK into @main

No configuration is required. The SDK detects your bundle identifier and Apple Team ID from the running process — there is no dashboard key or token to paste in. One call in your App init runs the whole bootstrap:

```swift
import SwiftUI
import AppAttest

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

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

Your app is identified by `(Team ID, bundle ID)` at the moment the SDK runs its first attestation. Production secrets only reach production-signed builds; sandbox secrets only reach development builds. The environment is inferred from the App Attest entitlement you added in step 1.

`AppAttest.start()` is synchronous and idempotent; the actual attest + sync run on a background `Task`. Cold-start reads after the first launch hydrate from Keychain before the first frame.

## 5. Read a secret

Access secrets anywhere via the synchronous subscript on the static `AppAttest` namespace:

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

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

For unhappy paths, switch on `AppAttest.state`:

```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):
            RetryView(error: err) { attest.retry() }
        }
    }
}
```

## Troubleshooting

### "Missing com.apple.developer.devicecheck.appattest-environment"

The entitlement was not added to the build. Re-check **Signing & Capabilities**. The App Attest capability should be present. If it is, delete `DerivedData` and build again:

1. **Xcode** → **Settings** → **Locations** → **Derived Data**. Click the arrow to open it in Finder.
2. Quit Xcode.
3. Delete the `DerivedData` folder.
4. Reopen and build.

### "App Attest not supported on this device"

You are on a simulator, or the device is older than iPhone 6s / iOS 14. For simulator development:

```swift
#if DEBUG
AppAttest.debugMode = .sandbox
#endif
AppAttest.start()
```

`.sandbox` hits real network but skips Apple attestation, and only returns development-environment secrets. For offline tests, use `.local(stubs: [...])` — for example:

```swift
#if DEBUG
AppAttest.debugMode = .local(stubs: [
    "OPENAI_API_KEY": "sk-test-stub",
    "BACKEND_KEY":   "dev-token-abc"
])
#endif
AppAttest.start()
```

Both modes are `#if DEBUG`-gated and physically absent from Release builds.

### "Attestation failed" at runtime

The bundle identifier, team, or App Attest capability on the Apple side is misaligned with what AppAttest expects. Check:

1. Dashboard bundle ID matches Xcode bundle ID exactly.
2. Apple Developer Console shows App Attest enabled on that identifier.
3. You are running a signed build, not an ad-hoc export.

Full error reference: <https://docs.appattest.dev/errors/attestation>.

## What's next

- [Entitlements reference](/docs/entitlements) — the exact file contents Xcode writes.
- [For AI agents](/docs/agents) — how to help a user complete this setup.