Skip to main content

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

For validation errors, an optional details object is included alongside the fields above:
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 on error.code, not on message or HTTP status.
  • error.code is stable and namespaced. It is the correct signal for programmatic branching.
  • error.message is human-readable prose that may change without notice. Never parse or switch on 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.unauthorized and session_token.invalid_or_expired return 401. Branching on the status alone is not precise enough to handle them differently.
Ignore unknown codes and fields. The API may introduce new error codes and new top-level fields on the 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 an x-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 as EndstateWalletError.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.
frame
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.
frame
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.
409
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 an Idempotency-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.