SDK / Mobile app
The iOS SDK identifies your app with Bundle ID + Team ID headers:/challenge, /validate, /refresh) need only these headers — the attestation object itself is the proof.
All other SDK endpoints — feature flags, the flag SSE stream, heartbeat, feedback, and support — additionally require one of:
- A Grantiva JWT issued by attestation, sent as
Authorization: Bearer <jwt>(this is what the SDK does automatically aftervalidateAttestation()), or - An API key (simulator/development mode, or server-side callers), sent as
Authorization: Bearer <key>.
401 Unauthorized with WWW-Authenticate: Bearer realm="grantiva".
If you send an API key and omit the identity headers, the request resolves to your organization but not to a specific app — feedback and flag responses then cover the whole organization rather than one app. Personal keys (gpat_) are the exception: feedback listings reject them without X-Bundle-ID and X-Team-ID (400 Bad Request).
In practice the SDK handles this for you: run
validateAttestation() once before using flags, feedback, or support. In the iOS Simulator (no App Attest), initialize the SDK with an API key instead — see Simulator Setup.Server-to-server
For backend integrations (analytics, VRT), use an API key:POST /api/v1/org/api-keys.
X-API-Key is accepted only on the analytics and VRT endpoints. The SDK endpoints and the /api/v1/org/* console endpoints read keys from Authorization: Bearer only.Console API (/api/v1/org/*)
The /api/v1/org/* endpoints back both the dashboard and the grantiva CLI, so they accept either a dashboard session cookie or an API key:
- API key —
Authorization: Bearer gpat_…/aat_…. The key’s scopes decide what it may do; org roles do not apply. - Session cookie — the dashboard path. Scopes do not apply; your org role does (viewer+ for reads, member+ or admin+ for writes, per endpoint).
apikey rate-limit bucket. See API Conventions.
Scopes
Each endpoint page lists the scopes it needs. Theapi:all scope satisfies every check, and legacy organization keys created before scopes existed are treated as full access.
A request whose key lacks the required scope returns
403 Forbidden with the required scope names in the message.