Overview
Every error response - regardless of endpoint or HTTP status - uses the same JSON envelope. Parse it once and reuse that logic across your integration.Error envelope
Response
Response
details object is included alongside the fields above:
Response
Response
string
A stable, namespaced error code in
<resource>.<reason> format (for example, chip.not_found). Branch your error-handling logic on this field - it is guaranteed not to change meaning.string
A human-readable description of the error. Useful for logging and debugging,
but do not parse or match against this string in code - its wording may
change without notice.
string
A unique identifier for the request, formatted as
req_<uuid>. This matches the X-Request-Id response header. Log it and include it in any support request so Endstate can locate the request in server-side logs.string
A URL pointing to the documentation for this error code (the error code
reference below).
object
Present only on
validation.failed responses. fieldErrors maps each
rejected field to its validation issues; formErrors carries issues that
apply to the request as a whole.Best practices
Branch onerror.code, not on message or HTTP status.
error.codeis stable and namespaced. It is the correct signal for programmatic branching.error.messageis human-readable prose that may change without notice. Never parse orswitchon it.- HTTP status codes are a useful coarse signal (see below), but multiple distinct error codes share the same HTTP status. For example, both
auth.unauthorizedandsession_token.invalid_or_expiredreturn401. Branching on the status alone is not precise enough to handle them differently.
error object over time. Write your error handler to treat any unrecognized code as a generic failure rather than throwing on an unexpected value.
Log request_id on every error.
The request_id field (also available as the X-Request-Id response header) uniquely identifies the server-side execution of that request. Always capture it alongside error details. When you contact support, include this value - it is the fastest way to locate and diagnose the problem.
HTTP status classes
The Endstate API uses standard HTTP status semantics. Branch on the class (the hundreds digit) rather than exact status codes.Plain HTTP requests receive a
426 Upgrade Required response. HTTPS is
required for all API calls.Error code reference
The catalogue below lists all error codes. Which codes a specific endpoint can return is declared per operation in the OpenAPI spec - each error response carries anx-error-codes list, and the reference page for the endpoint
renders the same information.
Authentication
401
Credential is missing or malformed.
403
Credential is valid but does not have access to the requested resource or
action.
Cross-organization resource access returns
404 rather than 403 to avoid
revealing that the resource exists.Session tokens
401
The session token is unknown, expired, or has been revoked.
403
The session token is bound to a different chip than the one in the request.
Wallet sessions
401
The session credential is unknown or has expired. Start a new session.
Wallet submissions
409
The operation has already been sent, has expired, or is not available in this
environment, so it cannot be sent.
Wallet frame
These codes come from the embedded wallet, not from an HTTP response. They reach your page asEndstateWalletError.code from @endstate-sdk/web.
frame | local
The page’s SDK speaks a protocol version the wallet does not support. Upgrade
the SDK.
frame
The wallet did not recognize the message. Upgrade the SDK.
frame
The operation is not available in this wallet yet. Upgrade the SDK.
frame
The embedding origin is not in your allowlist. Add it with
PUT /v1/settings/cors-origins, then reload.frame
An operation ran before the wallet session was established.
frame
The wallet session lapsed.
claim() and transfer() do not automatically
refresh or retry. Call EndstateWallet.refreshSession() or React’s
useWallet().refresh(), then retry the failed operation.frame
The identity token was refused: signature, audience, expiry, or reuse. Mint a
fresh one.
frame
The customer’s wallet could not be provisioned. Retry; contact support with
request_id if it persists.The wallet could not start in this browser, usually a privacy extension
blocking
wallet.endstate.io. Not recoverable in-page.frame
The wallet could not reach the Endstate API. Retry with backoff.
local
No answer within the configured budget. A timeout does not cancel the frame
request: it may still be preparing the operation, or a send may settle later.
Check the claim or transfer resource’s status before retrying.
local
The SDK’s
destroy() settled the call. Create a new wallet instance.The operation could not be gas-sponsored. Nothing was sent. Retry later; the
customer is never asked to fund the wallet.
frame
The wallet could not send an otherwise valid prepared operation.
details.cause names the reason, for example in_flight, expired, or
submit_timeout. Check the resource’s status before retrying.frame | local
Unhandled failure inside the wallet. Retry with backoff and include
request_id in support requests.Chips
404
The chip ID does not exist in your organization.
422
The tap credential could not be verified. The
e value or the tap
verification code (c) did not check out.422
Your organization requires the tap verification code (the tap’s
c value) to
pair a chip. Re-tap with a reader that captures it and retry.410
The tap credential has already been used. Each tap produces a single-use
credential - replay attempts are rejected.
400
A tap simulation was attempted on a real chip. The simulation endpoint is only
available for test chips.
409
The chip is already paired to a unit and cannot be paired again.
409
This chip cannot currently be verified. Contact support to restore
verification.
400
A bulk pairing request included units from more than one collection. Pair one
collection per request.
409
A bulk pairing for this collection is still processing. Retry once it
completes.
Chip replacements
409
The unit has been claimed - its chip is permanently locked and can no longer
be replaced.
409
A replacement is already open for this unit. Poll it until it settles.
404
No replacement with that ID exists for the unit.
Units
404
The unit ID does not exist in your organization.
409
A unit with the supplied
external_id already exists in the credential’s
environment.409
The unit has not been issued yet. Pair a chip and wait for
token.status: "active" first.409
One or more units already have an issuance in progress (for example, from
another pairing request). Wait for it to settle.
Claims
409
A claim is already open for this unit. Wait for it to settle before creating
another.
409
The unit is already owned by, or being claimed to, that address.
409
The unit’s current owner cannot be determined yet. Retry shortly.
404
No claim with that ID exists for the unit.
Transfers
409
A transfer is already open for this unit. Wait for it to settle before
starting another.
409
The unit is already held by that address.
409
The unit’s current owner cannot be determined yet. Retry shortly.
404
No transfer with that ID exists for the unit.
Collections
404
The collection ID does not exist in your organization.
409
A collection with the supplied
external_id already exists in the
credential’s environment.409
The collection has not finished provisioning. Wait until its status is
active before creating units inside it.Vaults
404
No vault with that ID is visible to your environment.
409
The credential’s environment already has a vault.
error.details.vault_id
names it.503
The vault could not be provisioned. Retry the same request; it resumes where
it stopped, or returns
vault.already_exists if another request finished it.403
The environment may not use this vault: it has no allowed connection to it, or
the vault belongs to your own organization.
The vault takes no new items. Either the connection to it is not active, or
the vault is not active because it is still provisioning, Endstate has
suspended or retired it, or its environment is archived. A vault that is not
active also cannot invite a brand or have a connection accepted.
error.details.vault_id names the vault.400
There is no live key challenge for this vault, or the signature does not come
from the submitted address. Request a new challenge and sign it with that
address.
409
The address already backs another vault or belongs to an account. Register a
different address.
409
The vault’s key changed while this request ran. Read the key, then retry if
you still want the change.
403
The receipt is not signed by the vault’s current key, names another request,
has expired, or expires in less than 10 minutes. Sign it again with the
current key.
error.details.reason says which.404
No vault connection with that ID is visible to your environment.
409
The vault connection is not active, so it takes no
default or allowed
setting.409
The environment already has a connection with that counterparty.
error.details.vault_connection_id names it and error.details.status gives
its status.409
The vault connection’s current state does not allow this change.
error.details.status names its status.Environments
409
The selected environment has invalid network or identity configuration. If the
message says the identity issuer and audience must be distinct, choose a
different issuer and audience for this environment; otherwise contact support.
Requests never fall back to another environment’s settings.
409
The environment cannot complete this change in its current state.
409
The active organization is not the one this request expected. Nothing changed;
reload the active organization before retrying.
Internal admin operations
Raised only by the platform’s own operations under/v1/admin/*, which live in the internal spec; partner integrations never receive them.
404
The organization named in the request does not exist.
409
The collection is not ready for this step: it is still being set up, an
earlier request for it is still running, or its network is not configured.
Idempotency
Returned by the calls that accept anIdempotency-Key. See Idempotency.
409
The key was already used with a different request body. Pick a new key; this
one cannot be replayed.
409
A request with this key is still running. Wait a moment and send the same
request again.
A failed call is never stored, so the same key retries through after an error.
You do not need a fresh key unless you are changing the request body.
Generic and rate limiting
400
The request body failed schema validation. See
error.details for per-field
issues.404
Generic resource not found. The requested resource does not exist within your
organization.
429
Your organization has exceeded a usage quota. Contact support.
429
Per-key rate limit exceeded, or rate limiting is temporarily unavailable.
Retry after
Retry-After.500
An unexpected server error occurred. Retry with exponential backoff and
include
request_id in any support request.Specific rate limits and quotas are set per organization during onboarding.
Treat any
429 as retryable with exponential backoff.Webhooks
404
The webhook endpoint does not exist in your organization.
410
The webhook endpoint was deleted and can no longer be changed.
409
Your organization already has the maximum of 16 webhook endpoints.
400
The endpoint URL must use https and point at a public host.
400
One of the event types is not a supported webhook event.
404
The webhook event does not exist in your organization.
Next steps
Pagination
Learn how to iterate through large result sets with cursor-based pagination.
API reference
Full request and response schemas for every endpoint.

