# appAttest

Ship the app. Not the keys.

Your iOS app fetches secrets from appAttest at runtime. They land in Keychain. They never ship in your binary.

## Example

```swift
import SwiftUI
import AppAttest

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

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

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

One synchronous setup call. Synchronous subscript for reads. Observable state for unhappy paths.

## The problem

The API keys your app calls out with end up inside the binary you ship. Anyone with the IPA and ten minutes can pull them out. Rotating after a leak doesn't help — the attacker already has what they wanted, and your new keys are one App Store update away from being extracted again.

## The fix

Apple ships a capability called App Attest. It lets a device prove to Apple that a given app binary is genuinely yours, signed by you, running unmodified on real hardware. appAttest takes that proof and uses it to release the right keys to the right install. The secrets never ship in your binary.

## How it works

1. **Your app attests itself.** At launch, the SDK asks iOS to generate an attestation. Apple's servers sign it: yes, this is the real signed app, running on a real device.
2. **appAttest verifies and releases.** The attestation goes to the appAttest API. If it checks out, the current set of secrets for the attested environment (sandbox or production) is returned.
3. **Keys land in Keychain.** The SDK writes secrets into the iOS Keychain, scoped to your app and the device that attested. Code reads them by name, e.g. `AppAttest.secrets["OPENAI_API_KEY"]` — synchronous, observable. Rotations pick up within ~15 minutes of foregrounding.

## Positioning

Not a vault. Not a secrets manager. Not a platform. appAttest does one job: attest your app, deliver your keys. Nothing to migrate to. Nothing to rewrite around.

## Pricing model

- Sandbox runs indefinitely for development.
- A project goes live by subscribing — subscribing IS the act of going live.
- Each subscribed project includes an allowance of requests per cycle; usage above that bills against published meter rates and a per-project prepaid balance.
- Subscription and balance are independent. Top up only when you want to.

## Features

- **Zero-config SDK.** Add the package. Write `AppAttest.start()` in your @main. Done.
- **Per-environment secrets.** Sandbox values flow to simulator and debug builds. Production values only reach attested, production-signed devices.
- **Rotation without redeploys.** Rotate a secret in the dashboard. Running apps pick it up within ~15 minutes of foregrounding.

## Links

- Sign up: <https://app.staging.appattest.dev/signup>
- Dashboard: <https://app.staging.appattest.dev>
- Quickstart: </docs/quickstart.md>
- Pricing: </pricing.md>
- Terms of service: </tos.md>
- Docs index: </docs/index.md>
- For AI agents: </docs/agents.md>
