# Attested bootstrap

:::note[Licensing in 2.0]

The SDK activates by attesting the app: Apple App Attest on iOS, hardware key attestation on Android. No license key is embedded. Register your app at `sdk.octetproof.com/apps` (below). A developer dashboard at `dashboard.octetproof.com` is on the way.

:::

## The model

In 2.0 the SDK proves the app's identity at first launch with a **platform attestation**, so no secret ships in the app:

| Platform | Anchor | Verified against |
|---|---|---|
| iOS | Apple App Attest | Apple's App Attest root |
| Android | Hardware key attestation | Google's hardware-attestation root (TEE / StrongBox) |
| macOS / Windows | Code-signing metadata | The platform signing authority |

The attestation names the app (its bundle identifier and Apple Team ID on iOS, its package name and signing-certificate SHA-256 on Android). Octet reads that identity from the verified attestation, looks it up in a registry of apps you own, and mints a license bound to the device. No secret ships in the app.

## Registering your app

You register each app once against your Octet account. Sign up at [sdk.octetproof.com/signup](https://sdk.octetproof.com/signup), then add your app identities at [sdk.octetproof.com/apps](https://sdk.octetproof.com/apps) (the onboarding email links there). The **Add an app identity** dialog records the bundle id and Apple Team ID on iOS, or the package name and signing-certificate SHA-256 on Android, with separate **Debug** (sandbox) and **Release** (production) certificate fields.

The first valid attestation that arrives for an unclaimed identity binds it to the account that registered it (race-to-attest). A contested identity is refused at bootstrap. A developer dashboard at `dashboard.octetproof.com` is on the way. `/apps` is the live surface today.

## The bootstrap handshake

`Octet.start(...)` runs this at first launch. It is a two-call flow so the server controls the anti-replay challenge:

1. **Challenge.** The SDK calls `POST /v1/bootstrap/challenge` and receives `{ challenge, expires_at }` (the challenge is single-use and expires in about 60 seconds).
2. **Attest.** The SDK folds the challenge into the attestation (`clientDataHash` on iOS, the key-attestation challenge extension on Android) and produces a signed attestation payload.
3. **Bootstrap.** The SDK calls `POST /v1/bootstrap` with the channel, the attestation, the key id, the challenge, the app identity, and the device fingerprint.
4. **Verify + mint.** The backend confirms the challenge is one it issued and burns it, verifies the attestation against the bundled platform root, looks up the app identity in your account's registry, checks the account is active, and mints a device-bound license plus an activation lease and bearer.

The bootstrap response is the same shape the old `/v1/activate` returned: a lease, an activation bearer, a license token, and the policy trust roots. Everything after bootstrap is unchanged from 1.x: the [lease refresh, offline grace, and revocation](/docs/concepts/license-activation/) all work exactly as before. Bootstrap replaces one entry point, not the lifecycle.

```mermaid
sequenceDiagram
    participant App as App + OctetSDK
    participant HW as App Attest / Key attestation
    participant LB as Octet license backend
    App->>LB: POST /v1/bootstrap/challenge
    LB-->>App: { challenge, expires_at }
    App->>HW: attest, challenge folded in
    HW-->>App: signed attestation
    App->>LB: POST /v1/bootstrap (attestation, identity, device_fp)
    LB->>LB: verify attestation + look up app + check account
    LB-->>App: { lease, activation_bearer, license_token, policy_trust_roots }
```

## Devices that cannot attest

An emulator, a CI runner, and a locally built debug app have no hardware attestation to offer. For those, register your Debug certificate under an app at `/apps` (the Debug field creates a sandbox identity), mint a **sandbox bypass token** for it there (a 7-, 14-, 30-, or 90-day lifetime, revocable), and pass it as `OctetConfig.sandboxBypassToken`. When it is set, the SDK bootstraps against that sandbox identity instead of attesting. A store or release build cannot use one: the SDK refuses a sandbox token in a production build, and the server refuses it for a production app.

A real device with no attestation and no sandbox token fails closed at `start` with a `LicenseError`. That is by design: without an attestation there is nothing to bind the license to.

## When bootstrap fails

`Octet.start(...)` throws a typed [`LicenseError`](/docs/api-reference/license-types/). A bootstrap-specific failure carries a `BootstrapReason` naming the cause: the challenge was not recognized, the challenge expired before use, the attestation did not verify, or the app identity is not registered. See [License Types](/docs/api-reference/license-types/) for the full case list.

## Where to go next

- [Prerequisites](/docs/getting-started/prerequisites/): register your app and set up attestation.
- [Device Attestation](/docs/concepts/device-attestation/): what App Attest and key attestation prove.
- [License & Activation](/docs/concepts/license-activation/): the lease, refresh, and revocation lifecycle after bootstrap.
