Errors & step-up

Errors & step-up

The error envelope

Plane errors are JSON with a stable machine-readable code:

{ "error": "not_found", "message": "no such item" }
HTTP error Meaning
400 invalid_body Missing/malformed body (e.g. no data object, no file part)
400 invalid_document Payload fails the schema it is validated against
400 unsupported_item Operation not defined for the slot’s kind (e.g. create on id_document)
401 unauthenticated No valid caller behind the token
403 partner_token_required A first-party (non-partner) token was presented
403 unknown_client The token’s client is no longer registered
403 organization_suspended Your owning organization is suspended
403 connection_inactive The connection is revoked, withdrawn, or expired
403 verb_not_granted The slot does not carry the verb this operation needs
403 delete_refused The platform protects this document from deletion
404 not_found Ungranted, foreign, malformed, or nonexistent — deliberately one answer
409 ambiguous_item The (kind, target) pair matches several slots / records — use slot ids
423 sealed The user’s vault is sealed; see step-up below
500 server_error Transient server failure — safe to retry
Info

Read 404 as “nothing for you here”, never as “does not exist.” The same answer covers an item the user declined to share, an id from another user, and an id that was never real. Probing teaches nothing — by design.

One 403 has a different body: the assurance gate (session security-level enforcement) answers with the platform’s GraphQL-style envelope — {"errors":[{"message":"…","extensions":{…}}]} with a machine-readable step-up code in extensions. Treat any 403 of that shape exactly like 423 sealed: run the step-up flow below, then retry.

Sealed vaults and step-up

Geena accounts have three security levels. Standard accounts are unsealed automatically when your tokens are issued — most calls just work. Enhanced and Maximum accounts keep the vault sealed until the user themselves provides fresh proof (face scan; plus PIN for Maximum). When a call needs the vault and it is sealed, you get 423 sealed.

Your app cannot unseal anything — but it can hand the user to Geena’s hosted verification page and retry afterwards:

1. Mint a step-up hand-off (authenticated with your access token):

POST https://api.test.geena.eu/oauth/step-up
Authorization: Bearer <access token>
{
  "handoffId": "9a1c…",
  "token": "b64u…",
  "requiredLevel": 2,
  "expiresAt": "2026-08-10T13:25:00Z"
}

The token is shown exactly once and is single-use.

2. Open the hosted verification page (popup, like the connect ceremony):

https://dashboard.test.geena.eu/verify/step-up?handoff=<handoffId>&token=<token>

The user completes the face scan (and PIN on Maximum accounts) on Geena’s origin. Completion stamps your partner session and unseals the vault.

3. Retry the refused call. No re-consent, no new tokens — the same access token now passes.

Minting a step-up for a Standard account answers 400 (“no step-up required”) — if you see it, the original failure was something else.