Skip to main content

Two layers of errors

Requests pass through an API gateway before they reach a service. The two layers fail differently, and only the service layer returns JSON.
The gateway enforces authentication. It rejects a missing, malformed, or wrong-audience token and answers in plain text, not JSON:API. A client that parses every error body as JSON will throw on the most common failure there is.

Gateway errors (plain text)

A missing token yields 403, not 401. Check the response Content-Type before parsing, because gateway errors are text/plain.

Service error format

Once a request reaches a service, errors follow the JSON:API error format, returned as an array in the errors field:
Only detail is guaranteed to be present. All other fields are optional.
The pointer is relative to the request body root (/email), not the JSON:API document root (/data/attributes/email).

Validation errors vary by service

Two different shapes are in use. Handle both. me-creds and me-wallet emit one error object per failed field, with code: "VALIDATION_ERROR" and a source.pointer. That is the shape above. consumer-onboarding and tenant provisioning emit a single BAD_REQUEST with the field paths under meta.issues:

HTTP status codes

Error codes

Not every service emits every code. The PRESENTATION_REQUEST_* codes come from the wallet API, and only some services emit FORBIDDEN, UNPROCESSABLE_ENTITY, and SERVICE_UNAVAILABLE.

Common errors

This is the most common failure. The gateway rejects it, so the body is plain text:
A missing Authorization header gives 403, not 401. A malformed token gives 401 Jwt header is an invalid JSON. A token for the wrong client gives 403 Audiences in Jwt are not allowed.Fix. Request a new access token from the authentication endpoint. Tokens expire after 300 seconds (five minutes).The wallet API returns the service-layer form instead:
The request body contains invalid data. Check source.pointer, or meta.issues[].path depending on the service, to identify the field.
On me-creds and me-wallet, the service returns one error object per failed field, so a request with three bad fields returns three entries in errors. On consumer-onboarding and tenant provisioning you get a single BAD_REQUEST object and the per-field detail sits in meta.issues[]. Read both. Iterate errors[] and, for each entry, check source.pointer first, falling back to meta.issues[].path.
The resource you referenced doesn’t exist. This typically means a template ID, user ID, or presentation ID is incorrect or has been deleted.
The resource you are trying to create already exists.
The holder tried to answer a presentation request past its expiresAt. Requests expire 15 minutes after creation, so generate them at the moment you’re ready to show them rather than in advance.Fix. Create a new presentation request and deliver it again. You cannot revive the expired one.
The holder has no credential that satisfies the request. Either they don’t hold the credential type you asked for, or the credentials they hold don’t come from an issuer you accept.Fix. This is not a client bug. Check the preset’s trusted issuers against what the holder has.
An unexpected error occurred on the server. Your request did not cause it. Retry after a short delay, and contact support if the problem persists.