Errors and Correlation IDs
XKOVA returns a single, stable error envelope on every failure. This catalog lists the 155 live error codes by HTTP status. It is generated from the contract, so it stays in step with the API.
Every error response carries the same envelope: a stable
code, a human readable message, an optional details object, and a correlation_id to quote when contacting support. The retry column states whether retrying can change the outcome.Retry semantics
| Value | Meaning |
|---|---|
none | Not retryable. Retrying will not change the outcome. |
immediate | Retry right away, for example after a transient network blip. |
header | Wait the seconds given in the Retry-After response header. |
details | Wait the seconds given in details.retry_after_seconds. |
400 4
| Code | Retry | When it occurs |
|---|---|---|
faucet_chain_not_supported | no | On-ramp under the faucet profile targeted a non-Fuji chain. Faucet is testnet-only by construction. |
ramp_profile_not_allowed | no | Caller passed x-ramp-profile header naming a profile not in the tenant's ramp_profiles_allowed list. Production tenants reject any non-default value here. |
stripe_request_error | no | the billing provider API rejected caller-supplied input deterministically (unknown price id, malformed customer params, invalid resource state). Retrying with the same payload will not help; fix the input. |
webhook_malformed | no | An inbound provider webhook signature envelope or body is structurally malformed, an un-splittable t=…,v1=… header, a non-numeric timestamp, a body that is not parseable JSON, or a payload missing contract-required fields. Deterministic; retrying the same bytes will not help. |
401 13
| Code | Retry | When it occurs |
|---|---|---|
active_scope_unavailable | yes (immediate) | JWT verifies and the operator has staff rows, but the X-Active-Scope header points at a scope the operator cannot act in. Typically a stale localStorage pointer from a recent account switch, clients should clear their cached scope and re-pick from /v1/me/scopes before retrying. |
member_auth_invalid_credentials | no | POST /v1/member-auth/sign-in with email + password that XKOVA IDP rejected. |
member_auth_mfa_challenge_invalid | no | The mfa_token supplied to POST /v1/member-auth/sign-in/mfa/verify is unknown, expired, already used, or out of attempts. Start the sign-in over. |
member_auth_mfa_code_invalid | no | The TOTP code supplied to POST /v1/member-auth/sign-in/mfa/verify was rejected. The challenge is still open, the member can retry the code until the attempt cap is reached. |
member_auth_refresh_replay_detected | no | POST /v1/member-auth/refresh received a refresh token whose hash matches a member_sessions previous_refresh_token_hash, token was already rotated. The whole refresh-token family is revoked at the same time. |
member_auth_session_revoked | no | A sensitive endpoint (sign-out / profile mutation / refresh) loaded a member_sessions row that is revoked. Caller must start a fresh sign-in. |
member_auth_token_invalid | no | Email-link token (signup / magic-link / password-reset / refresh) does not exist or was already consumed. |
request_signature_invalid | no | The api key has require_signed_requests enabled (opt-in #14) but the request's X-Signature HMAC header was missing/malformed, outside the ±5-minute replay window, or did not match (tampered body/path/method, or wrong signing secret). The same body+signature will never pass, fix the signature, don't retry verbatim. Implemented by RequestSignatureInvalidError. |
stripe_webhook_signature_invalid | no | the billing provider webhook signature header failed verification. Check that on the receiver matches the signing secret of the registered webhook endpoint in the billing provider. |
unauthorized | yes (immediate) | No valid authentication credential on the request. |
unauthorized_account_holder | no | SSO assertion arrived with no matching federated_identity row and JIT is disabled for this tenant. |
webhook_signature_invalid | no | An inbound provider webhook (banking / coinbase / conversion / engine / screening / XKOVA IDP hook) arrived with a missing, stale (clock drift outside the replay window), or tampered HMAC signature. The caller failed to authenticate; the same body+signature will never pass, so the provider should stop retrying. |
workspace_scope_unavailable | yes (immediate) | The operator scope resolved, but the X-Workspace-Id header points at a workspace that is inactive/deleted or belongs to a different tenant. Typically a stale localStorage pointer from a same-user account switch, clients should clear their cached workspace id and retry (the api then defaults to the tenant's sandbox workspace). A cross-tenant workspace id is always rejected here, so this is never a data leak. |
403 25
| Code | Retry | When it occurs |
|---|---|---|
activation_code_tenant_mismatch | no | Activation code was issued with a scoped_tenant_id that doesn't match the caller's tenant. Distinct from _invalid so support can tell apart "you typed the wrong code" from "this code was issued to someone else." |
authorization_denied | no | The value movement was denied by the workspace authorization policy (the TAP firewall), a tenant-configured rule, not a sanctions/screening block. Reason code in details. Fail-closed: a missing/uncommitted policy denies everything. |
bootstrap_requires_staff_jwt | no | The first admin role grant on a tenant must be authorized via staff JWT auth (recorded as bootstrap_via='staff_jwt'). A request authenticating via an api-key-role grant attempting to bootstrap the first admin gets this 403. |
compliance_blocked | no | Pre-sign compliance pipeline hard-blocked the tx. Reason code in details. |
contacts_disabled | no | Tenant has disabled the recipient directory (contacts) feature via tenants.contacts_disabled=true. Writes return this error; reads may still proceed with cached results. |
csrf_missing_or_invalid | yes (immediate) | Double-submit CSRF cookie/header pair missing or mismatched on a cookie-authed request. Cleared by reloading the page so the client can pick up a fresh CSRF token. |
demo_login_disabled | no | POST /v1/demo-login was called but the demo-login feature is off in this environment. The effective gate is =true OR xkovaEnv ∈ {local,test,demo}; staging/prod return this unless the public demo deployment explicitly opts in. |
feature_not_enabled | no | Tenant lacks the entitlement required to invoke this surface. details.feature carries the feature key (e.g. 'treasury'); the workspace-console renders this as a "redeem an activation code to unlock <X>" prompt. |
first_use_required | yes (immediate) | Account holder has not completed first-use consent for all required tenant policies; outbound ops blocked until accepted. |
member_auth_account_holder_inactive | no | Sign-in succeeded against XKOVA IDP but the bound account_holder is in a non-active state (held / closed). |
member_auth_email_not_confirmed | no | Sign-in attempted before the member clicked the confirmation link in their email. |
member_auth_mfa_required | no | A member-scoped request presented an identity token that has not passed a second factor, for a member who has a verified MFA factor. Complete the sign-in MFA challenge (POST /v1/member-auth/sign-in/mfa/verify) and use the token it returns. |
member_auth_origin_not_allowlisted | no | Browser-side caller of /v1/member-auth/* presented a public api key but the request Origin header is not in tenant_member_auth_allowed_origins. |
not_a_treasury_signer | no | Caller has a treasury role but is not a member of this specific Safe wallet, so cannot draft, sign, or execute on it. |
partner_scope_denied | no | Partner-scoped API key attempted an action that requires tenant-scoped authorization (sensitive actions cannot be performed from a partner key). |
platform_partner_required | no | A partner-scoped admin caller attempted a cross-tenant PLATFORM operation, the production-access admin review queue, but its partner is not the XKOVA platform partner (partners.is_platform = false). Review is XKOVA-central; a non-platform partner cannot list, approve, or reject another partner's tenants' requests. |
role_denied | no | Staff role does not permit this action (per §21.6 override matrix). Audit event written. |
rwa_document_not_entitled | no | A GET /v1/rwa/tokens/{id}/documents/{docId} for a GATED document named a grantee with no ACTIVE document-entitlement grant on the token. Gated documents are reachable only by a recipient the issuer granted via POST.../document-entitlements. Grant the recipient, or request a public document. |
self_approval_forbidden | no | Separation of duties: the staff member who INITIATED an approval request attempted to also attest it toward the same quorum, while the clause is satisfiable WITHOUT them (the group has enough other active members to meet the threshold). A solo-owner clause that cannot resolve without the initiator is still allowed. |
step_up_required | yes (immediate) | Action requires step-up re-auth. Two distinct trigger contexts: (1) Operator JWT path, staff session is AAL1 with TOTP enrolled, sensitive actions (API-key rotation, HMAC rotation, staff-role change, fee-schedule commit, custody config, merchant-role approval above threshold, high-value tx, treasury mutations) require a fresh AAL2 challenge. (2) API-key path, an api-key was minted without mfa_elevated_at_creation: true and is now hitting an AAL2-gated route (notably treasury mutations). Resolution: rotate the key under an AAL2-elevated operator session so the replacement carries the MFA-elevation flag for its lifetime. |
system_key_revoke_denied | no | Role-gated revocation refused: the target api key is system: true (XKOVA-internal demo / dev seeder). Operator JWTs can revoke; api-key-role auth cannot. |
tenant_read_only | no | Tenant is in read-only mode (billing past due T+30 per §10.1 dunning). Inbound receive + staff ops still work. |
tokenization_not_authorized | no | A tokenization mutation (e.g. POST /v1/tokenization/erc20) was called by a correctly-scoped tenant key that lacks the tokenize_admin role. Distinct from partner_scope_denied (wrong scope), this is a role gap inside the right tenant. Grant the key the tokenize_admin role and retry. |
treasury_role_denied | no | Caller is authenticated but lacks the treasury role required for this operation (treasury_admin / treasury_signer / treasury_viewer). |
verification_required | no | The account holder must complete identity verification (KYC/KYB) before a signing session is issued; the account verification_status is not cleared (unverified / in_progress / awaiting_approval / rejected / expired). |
404 8
| Code | Retry | When it occurs |
|---|---|---|
email_domain_not_found | no | No sending domain with the given id exists for the caller's tenant (never created, already removed, or belongs to another tenant, RLS-scoped). |
not_found | no | Referenced resource does not exist, or is not visible to the caller under RLS. |
revenue_treasury_change_not_found | no | The revenue-treasury change request does not exist (or is not in the caller's tenant). |
tokenization_access_registry_not_set | no | A POST /v1/tokenization/tokens/{id}/access-list/{add,remove} request targeted a token that has no access registry, it was deployed without accessControl, so transfers are open and there is no allow/deny list to manage. Attach a registry first via POST /v1/tokenization/tokens/{id}/access-registry (or deploy a token with accessControl: allowlist|denylist), then manage membership here. |
tokenization_authority_not_found | no | A token-authority route (/v1/tokenization/authorities/{id}*) named an authority id that does not exist in the caller's workspace, a genuine miss or a cross-workspace id (RLS + the workspace filter make the two indistinguishable by design). Register the authority, or use an id from GET /v1/tokenization/authorities. |
treasury_counterparty_not_found | no | No counterparty exists for the given id under the active tenant scope. |
treasury_transfer_not_found | no | No treasury transfer exists for the given id under the active tenant scope. |
treasury_wallet_not_found | no | No treasury wallet exists for the given id under the active tenant scope. |
409 36
| Code | Retry | When it occurs |
|---|---|---|
activation_code_already_redeemed | no | Activation code was previously redeemed by some tenant. Codes are one-time-use; issue a new code. |
asset_schema_fork_conflict | no | A fork of a curated platform template into a workspace draft named a new_schema_key that already exists as a schema in the target workspace (the workspaceKeyUniq partial unique on asset_schemas). Supply a distinct key to fork the same template a second time. |
attester_already_registered | no | Registering an attester collided with an already-ACTIVE registration for the same (workspace, scope, address). Revoke the existing registration first, or reuse it. details.attester_address names the address. |
attester_revoked | no | An attested append-log entry named an attester whose registration has been revoked, or a revoke targeted an already-revoked registration. A revoked party can no longer attest. Register a fresh attester. |
authorization_requires_approval | no | The value movement matched a require_approval policy rule and cannot proceed until its compound approver quorum signs off. |
break_glass_required | no | A governed action requires quorum but the configured approval clause is UNSATISFIABLE (the approver group has fewer active members than the clause threshold), so the request would strand and never resolve. The action cannot proceed normally; an owner must arm a break-glass session (the time-boxed emergency-access safety valve) to bypass it. |
compliance_case_already_open | no | The payment already has an OPEN compliance case. Flagging again would stack a second case on one subject and split the review trail across two records; act on the existing case instead. |
compliance_case_open | yes (immediate) | An operator-visible compliance case (state open/investigating/escalated) is targeting this transaction. Signing URL issuance is blocked until the case is transitioned to closed or sar_filed. |
conflict_state | no | Requested state transition is not valid from the resource's current state. |
earned_beneficiary_unset | no | The fee resolver found a committed earned rule with no (or zero) on-chain beneficiary address. Earned rules are stamped to the revenue treasury at commit, so this indicates a schedule committed without a revenue treasury; the resolver refuses to burn fees to the zero address. |
email_domain_conflict | no | The sending domain is already registered for this tenant. Re-adding an existing domain is a no-op conflict; verify or remove the existing one. |
force_cancel_blocked | no | The operator force-cancel of a post-sign payment was refused by the on-chain reality check. The transfer either executed successfully on-chain (money moved, never cancellable) or its outcome cannot be resolved yet (still in flight / RPC lag). Only a reverted or never-submitted transfer can be force-cancelled; the operator retries once the chain settles. |
idempotency_conflict | no | Same Idempotency-Key replayed with a different request body (within 24h window). |
insufficient_funds | no | Core-banking declined a debit because the source bank account lacks the funds. Distinct from insufficient_balance (the on-chain wallet check). |
invalid_state | no | A service rejected the requested transition: the source row exists but is not in a state from which the requested action is allowed (e.g. PATCH state=closed → state=active on an account_holder; confirmAvatar with an object_key whose tenant prefix does not match the holder; jitProvision into a tenant whose oidc_provider_kind is misconfigured for the field passed). The error message names the precondition that failed. |
rebind_invalid_state | no | A schema-rebind ceremony stage was invoked from the wrong lifecycle state, e.g. attest before a passing dry-run, or commit before attest. The ceremony advances proposed -> dry_run_passed -> attested -> committed. details.{current,required} name the states. Run the missing prior stage. |
revenue_treasury_already_designated | no | The workspace already has a different designated revenue treasury. Re-pointing revenue to another Safe requires the quorum-gated revenue-treasury-change flow, not a second designation. |
revenue_treasury_change_in_progress | no | A non-terminal revenue-treasury change request already exists for the workspace; concurrent re-links are blocked. Cancel or complete it first. |
revenue_treasury_change_state_invalid | no | The change request is in a state incompatible with the requested action (signing a non-open request, applying one below threshold, or acting on an applied/cancelled request). |
revenue_treasury_in_use | no | Tried to decommission a Safe that is the active revenue treasury of a workspace with a committed earned-fee schedule routing to it. Re-link revenue to another Safe first (guards against orphaning earned-fee routing to a dead Safe). |
revenue_treasury_required | no | Tried to commit a fee schedule carrying earned-fee rules (institution / external / issuer) but the workspace has no active revenue treasury to route them to. Establish + designate a treasury Safe first, revenue cannot be enabled without a treasury (the hard gate). |
signer_mpc_not_provisioned | yes (details) | One or more requested signers have not yet provisioned their Thirdweb in-app MPC wallet (state != active). Prompt the staff member(s) to set up their treasury signing key first. details.staff_ids carries the unprovisioned ids. |
token_already_bound | no | Binding a published asset schema to a token (POST /v1/rwa/tokens/{id}/schema-binding) was rejected because the token is already bound to a DIFFERENT asset-schema version. A token binds one schema version, set once; changing it is the quorum-gated schema-rebind ceremony, not a re-bind. Re-binding to the SAME version is idempotent. |
token_transfer_frozen | no | A platform-relayed NFT transfer was rejected because the token is under an ACTIVE transfer freeze (e.g. a corporate action in flight). The freeze is a hard gate checked before the graded door. Retry after the freeze is lifted. |
tokenization_access_registry_exists | no | A POST /v1/tokenization/tokens/{id}/access-registry request targeted a token that ALREADY carries an access registry (access_registry_proxy_address is set, at deploy with accessControl, or by a prior enable). A token has exactly one registry; the attach is a one-time act. Manage membership via /access-list/{add,remove}; the registry itself is not replaceable through this surface. details.access_registry_proxy_address names the existing registry. |
tokenization_authority_already_verified | no | A proof-of-possession was submitted for an authority that is already verified (verified_at set). A server_key is provable exactly once, its first successful proof is permanent; a safe authority is verified at registration. Returned instead of silently re-stamping verified_at, so a replayed proof is observably a no-op conflict. |
tokenization_invalid_state_transition | no | A lifecycle transition was requested from a state that does not allow it, e.g. POST /v1/tokenization/tokens/{id}/deprecate on a token that is not active. Only an active, rails-registered token can be deprecated; a token still deploying or in a failed state has no rails entry to retire. The details.current_state field names the state the token was actually in. |
tokenization_operation_already_relayed | no | A signature (or a duplicate relay) was submitted for a tokenization operation that has already collected its Safe threshold and been relayed to Engine, its Safe.execTransaction is in flight or terminal. Returned instead of burning relayer gas on a guaranteed-revert resubmission; the on-chain Safe nonce is the backstop. Poll the operation for its terminal status. |
tokenization_operation_pending | no | A token deploy was requested while a prior deploy operation for the same workspace is still in flight (a tokenization_operations row, kind=deploy, in a non-terminal state). Returned instead of enqueuing a second on-chain deploy that would burn relayer gas. Replaying the SAME request body returns the original operation via the idempotency cache; a DIFFERENT body is rejected here until the in-flight deploy resolves. |
treasury_asset_already_registered | no | An asset for this (chain, token_address) is already in the registry. |
treasury_counterparty_address_collision | no | The address is already mapped to a different counterparty on this chain. UNIQUE(chain_id, address) on treasury_counterparty_addresses prevents ambiguous tax aggregation. |
treasury_safe_nonce_exhausted | no | Safe nonce reservation collided with on-chain state in a way the API cannot resolve automatically (rare; indicates the Safe was used outside XKOVA's API). Operator must reconcile manually before drafting more transfers. |
treasury_signature_already_submitted | no | This signer has already submitted a signature for this transfer. |
treasury_threshold_not_met | yes (none) | Execute called but signature count is below the Safe's threshold. details.signature_count and details.threshold are populated. |
treasury_transfer_nonce_blocked | no | Refused execTransaction submission because on-chain Safe.nonce() doesn't match this transfer's safe_nonce. Safe consumes nonces in strict order, submitting at a higher nonce reverts on chain with GS026. details.transfer_safe_nonce, details.on_chain_safe_nonce, and details.blocking_transfer_ids are populated; the operator must execute or cancel the blocking transfers before this one can run. |
treasury_transfer_state_invalid | no | The transfer is not in the state the requested operation requires (e.g., execute called on a transfer not in ready_to_execute, or signature submitted on a transfer in submitted/confirmed). details.current_state and details.required_states are populated. |
410 1
| Code | Retry | When it occurs |
|---|---|---|
member_auth_token_expired | no | Email-link token is past its TTL. Caller should request a fresh link. |
422 47
| Code | Retry | When it occurs |
|---|---|---|
account_frozen | no | Core-banking declined a mutating call because the account is frozen, locked, or closed. Surfaced on ramp deposit/withdrawal so the failure is actionable rather than a generic 500. Mirrors banking-core's account_frozen. |
activation_code_expired | no | Activation code carries an expires_at in the past. |
activation_code_invalid | no | Activation code didn't match any known row (sha256 lookup miss). The plaintext code may be mistyped. |
attester_scope_mismatch | no | A registered attester pinned to a SPECIFIC token was used to attest a fact on a different token. A token-pinned attester signs only for its token; register the attester workspace-wide (no token) or for the right token. |
attester_signature_mismatch | no | An attested append-log entry carried a signature that, recomputed server-side over the canonical EIP-712 fact digest, recovered to a DIFFERENT address than the named registered attester. The entry is NOT this attester's; no append-log row and no provenance version are written. details.{recovered,expected} name the addresses. Sign the exact {tokenRef, fieldPath, payloadDigest} digest with the registered key. |
authorization_invalid | no | The submission layer could not confirm the signed authorization for this value movement (missing, expired, already consumed, or the rebuilt intent no longer binds the signed digest). The relay is refused. |
billing_tier_not_chargeable | no | Tenant plan has no the billing provider subscription path. Either the plan is base (free tier, no subscription is created by design) or a paid plan is missing its STRIPE_PRICE_*_* env config. Operator must either upgrade the tenant to a chargeable plan or fix the deploy config. |
chain_not_allowed | no | The optional per-request chain_ref override named a chain this resource cannot use (it does not match the workspace's pinned chain / the referenced wallet or token's chain). Omit chain_ref to use the resolved chain. |
email_domain_invalid | no | The submitted sending domain was rejected by the provider as invalid (malformed domain name, or a domain the provider will not accept). Deterministic; the same domain will not pass on retry. |
member_auth_invalid_email | no | Email field on sign-up / sign-in / etc. is not a parseable address. |
member_auth_redirect_url_not_allowlisted | no | redirect_to URL on a sign-up / magic-link / password-reset request is not in the tenant's tenant_member_auth_redirect_urls allowlist. |
member_auth_weak_password | no | Password supplied to sign-up / password-reset/confirm / profile (PATCH) does not meet XKOVA IDP policy. |
nft_not_owned_by_safe | no | A treasury-Safe NFT transfer was drafted for an NFT the Safe does not own, the on-chain ownerOf (ERC-721) / balanceOf (ERC-1155) read at draft shows the Safe is not the holder, so no op is created. Draft a transfer only for an NFT the Safe currently custodies (see GET /v1/treasury/wallets/{id}/nft-holdings). |
off_ramp_burn_unverified | no | The client-supplied off-ramp burn transaction could not be verified on-chain, so the fiat conversion was refused. The burn must be a successful transfer of the exact stablecoin amount from the source wallet to the partner redeem address, not previously consumed by another off-ramp. Reason in details. Terminal (a mined receipt will not change), build + submit a correct burn. |
rebind_attestation_invalid | no | A schema-rebind commit re-verified the stored EIP-712 attestation against the recomputed proposal digest and the signature recovered to a DIFFERENT address than the one recorded at attest, the proposal (transform, token set, or dry-run result) was tampered with after attestation. The commit is refused and the ceremony is poisoned to failed. Open a fresh ceremony. |
rebind_not_forward_only | no | A schema-rebind ceremony was proposed against a target asset_schema_version that is not a strictly-LATER published version of the SAME asset schema, or against a token not currently bound to the source version, a binding can only move forward. details.reason names the failing leg (different_schema, target_not_published, source_not_published, not_forward). Pick a later published version of the same schema. |
revenue_treasury_change_target_invalid | no | The proposed re-link target is invalid: the workspace has no current revenue treasury to change from, or the target Safe is not active / not in the same workspace / is the Safe already designated. |
revenue_treasury_not_active | no | Tried to designate a treasury Safe as the revenue treasury, but the Safe is not active (still deploying or decommissioned). Only an active Safe can hold pushed earned fees. |
role_not_allowed_for_service_account | no | A POST /v1/tokenization/tokens/{id}/roles/grant named a target authority whose purpose is service_account. A service account is an integrator-held automation EOA that may hold ONLY a policy-capped MINTER, granted via POST /v1/tokenization/tokens/{id}/policies (the Zodiac scoped role). The raw grantRole path produces an UNCAPPED on-chain role, so it is forbidden for a service account regardless of which role was named (even ). Grant the capped MINTER via the policies endpoint instead. 422. |
rwa_document_hash_mismatch | no | A POST /v1/rwa/tokens/{id}/documents/confirm declared an expected content hash that does not match the SHA-256 the server recomputed from the uploaded object bytes. The integrity anchor is server-derived; a mismatch means the bytes are not what the caller claims, so no rwa_media_objects row is written and no metadata commit occurs. Re-upload and confirm with the correct hash. |
rwa_relationship_cycle | no | Committing a token→token relationship field where the target token is already reachable from the source token, or the edge is a self-reference. token→token edges must stay acyclic so the resolver can compose linked assets without infinite expansion. Remove the edge that would close the loop. |
rwa_relationship_external_ref_invalid | no | An external relationship reference was malformed: its system is not in the field's allowed-system enum, or its id failed the field's RE2 id pattern. Use an allowed system + a well-formed id. |
rwa_relationship_target_not_found | no | A relationship field referenced a target that does not resolve: a token target that is not a live customer token in your workspace (missing, cross-workspace, or deprecated/terminal), or an identity target that is not an existing in-tenant subject (account-holder / token-authority). Reference a live in-workspace target. |
rwa_schema_unsafe | no | Publishing an RWA asset_schema_version (palette-compiled OR raw-upload) failed the publish gate: the schema embeds a construct that could DoS validation, exfiltrate via an external reference, or smuggle an unbounded structure. details.reason names the failing class (meta_schema, external_ref, unbounded_string, unbounded_array, open_object, disallowed_format, redos_pattern, combinator_breadth, node_count, depth, precompile). Fix the schema (the palette path is safe-by-construction) and re-publish. |
token_deploy_failed | no | An on-chain token-deploy transaction reverted, Engine reported the tx mined but onChainTxStatus == 0, or the chain receipt status is reverted. The customer_tokens row is left in a non-active state for operator review; the operation row records the failure. Deterministic for the same inputs, so it is not auto-retried. |
tokenization_authority_kind_mismatch | no | A proof endpoint (/proof-challenge or /proof) was called on an authority whose kind does not support it, the proof-of-possession flow applies ONLY to a server_key authority. A safe authority's control is its on-chain N-of-M owner set (verified at registration), so it has no PoP step. details.kind names the authority's actual kind. |
tokenization_authority_not_verified | no | A POST /v1/tokenization/tokens/{id}/policies request named a server_key authority that has not completed proof-of-possession (verified_at is null). A server key must prove control of its EOA before it can hold a scoped mint role, otherwise an unattended capped-mint authority could be granted to an address the customer does not control. Verify the authority via POST /v1/tokenization/authorities/{id}/proof first, then re-issue. |
tokenization_operation_not_signable | no | A signature submitted to POST /v1/tokenization/tokens/{id}/operations/{opId}/signatures was rejected: the recovered signer is not an active owner of the token's admin Safe (checked against on-chain Safe.getOwners()), the supplied signer address does not match the ecrecover result, or the signature is malformed. Only the admin Safe's N-of-M owners can authorize a mint. Re-sign with an owner key. |
tokenization_policy_exceeded | no | A POST /v1/tokenization/tokens/{id}/policy-mint requested an amount greater than the confirmed on-chain per-call cap for the (token, authority) mint policy. Rejected BEFORE relaying, the Roles Modifier would revert it on-chain anyway; this fail-fast avoids the wasted relay. The cap is read from the DB mirror (defense-in-depth; the on-chain revert is authoritative). |
tokenization_policy_not_set | no | A POST /v1/tokenization/tokens/{id}/policy-mint request named a (token, authority) pair with no CONFIRMED mint-cap policy, none was set, or the set_mint_policy operation has not yet confirmed on-chain. Without the on-chain Roles scope the relayed mint would revert NotAuthorized, so this fails fast. Set a policy via POST /v1/tokenization/tokens/{id}/policies and wait for it to confirm before minting. |
tokenization_proof_invalid | no | A proof-of-possession submitted to POST /v1/tokenization/authorities/{id}/proof was rejected: the challenge token's HMAC failed verification (tampered / forged / minted with a rotated secret), the challenge expired, it was minted for a different authority or workspace, or the signature did not recover to the authority's registered address. Control of the server-key EOA is the gate on verifying it. Re-issue a fresh challenge and sign the returned message with the authority's key. |
tokenization_raw_call_invalid | no | A POST /v1/tokenization/tokens/{id}/raw-safe-tx (the advanced governance escape hatch) named a contract outside the token's own three control contracts (token / access_registry / roles_modifier), a function not in the resolved contract's committed ABI, or args whose count/shape does not match the function inputs. The escape hatch is scoped to ONE of the token's own contracts (server-resolved address; an arbitrary address is impossible) and the inner calldata is encoded from the committed ABI, so a call that cannot encode against a real function is rejected before any SafeTx is built. Re-issue with a contract from the enum + a function in its ABI + the correct arg count. |
tokenization_recover_target_active | no | A POST /v1/tokenization/tokens/{id}/recover request targeted an account that still has access to the token (accessRegistry.hasAccess(account) is true). recoverTokens is a compliance claw-back that applies ONLY to a blocked account, the on-chain pre-condition is hasAccess == false, else it reverts RecoveryOnActiveAccount. Returned as a pre-flight so the request fails fast rather than relaying a guaranteed-revert SafeTx. Block the account first (denylist: /access-list/add; allowlist: /access-list/remove), then recover. details.account names the target. |
tokenization_relay_expired | no | A POST /v1/tokenization/tokens/{id}/gasless/relay request carried a forwardRequest whose deadline has already passed. The on-chain forwarder rejects an expired request (ERC2771ForwarderExpiredRequest), so this fails fast rather than relaying a guaranteed revert. Build a fresh transfer-request (which stamps a new deadline + current nonce) and re-sign. |
tokenization_relay_invalid_signature | no | A POST /v1/tokenization/tokens/{id}/gasless/relay request carried a forwardRequest + signature whose EIP-712 signature did not recover to forwardRequest.from (tampered request, wrong signer, or malformed signature). The holder's signature is the sole authorization for moving their balance; the on-chain forwarder rejects a mismatch (ERC2771ForwarderInvalidSigner), so this fails fast before relaying. Build a fresh transfer-request and re-sign it verbatim. |
tokenization_relay_wrong_target | no | A POST /v1/tokenization/tokens/{id}/gasless/relay request carried a forwardRequest whose inner-call target (to) is not this token's proxy. The relay surface is scoped to the single token in the path; a request targeting a different contract is rejected before relay so the integrator's relay key cannot sponsor an arbitrary call through the shared forwarder. Build the transfer-request via this token's.../gasless/transfer-request endpoint and re-sign. |
tokenization_unknown_role | no | A POST /v1/tokenization/tokens/{id}/roles/{grant,revoke} request named a role that is not in the closed ERC20X role set (,,,,,, ). Rejected before any SafeTx is built, a grantRole of an unknown role id would silently create a new, never-checked role on-chain rather than revert. Re-issue with a known role name. |
tokenization_unsupported_decimals | no | POST /v1/tokenization/erc20 requested a decimals other than 18. ERC20X does not override OpenZeppelin default decimals, so every deployed token is 18-decimal on-chain; a non-18 request would silently produce an 18-decimal token. Rejected up-front. Re-issue with decimals: 18. |
transfer_eligibility_denied | no | A platform-relayed NFT transfer (holder-gasless OR treasury-Safe) was rejected by the graded transfer door before any Engine relay/execute: a declarative eligibility rule on the asset schema denied it. details.reason_code names the failing rule (holder_cap_exceeded, jurisdiction_not_allowed, jurisdiction_denied, accreditation_required, lockup_active). Satisfy the rule (e.g. attest the recipient's accreditation via the holder-eligibility store, or wait out the lockup) and retry. |
treasury_asset_not_registered | no | The asset_token_address is not in treasury_supported_assets for this chain. A treasury_admin must register the ERC-20 first via POST /v1/treasury/assets. |
treasury_chain_unsupported | no | The requested chain_id has no Safe ProxyFactory + Mastercopy entries in supported-chains.ts. Treasury is not supported on this chain yet. |
treasury_replicate_target_invalid | no | POST /v1/treasury/wallets/:id/replicate target list contains the source chain_id, an unsupported chain, or a chain that already has a sibling treasury wallet for this tenant. |
treasury_signature_invalid | no | ecrecover of the submitted signature does not match the signer staff member's known MPC address. Either the signature is malformed, or the staff member's MPC wallet has not been provisioned to the address recorded in staff_mpc_wallets. |
treasury_signer_count_below_minimum | no | Wallet creation or signer-removal would leave signer_count < threshold + 2 (lost-signer policy). details.required_minimum and details.current_signer_count are populated. |
treasury_threshold_invalid | no | Requested threshold is outside [1, signer_count - 2]. details.attempted_threshold and details.signer_count are populated. |
unknown_event | no | A webhook endpoint was created or updated subscribing to an event name that is not in the webhook event-type registry (GET /v1/webhook-event-types). Subscribe only to registered events. |
validation_failed | no | Request body or query failed schema validation (ValidationPipe). |
429 2
| Code | Retry | When it occurs |
|---|---|---|
faucet_daily_cap_exceeded | no | Faucet daily cap exceeded for the destination wallet. Default $1,000 USD per UTC-day per wallet; configurable via. |
member_auth_rate_limited | yes (header) | Per-tuple rate limit on a sign-in / sign-up / magic-link-request / password-reset-request endpoint tripped. |
500 2
| Code | Retry | When it occurs |
|---|---|---|
configuration_error | no | A startup-time invariant between env / registry / chain state disagrees, e.g., an RPC-reported chainId differs from the entry (drift-defense §0.1), a deployed router address does not match the registry (§0.2), on-chain token decimals disagree with the token registry (§0.4), or the router EIP-712 domain separator disagrees with the static domain (§0.5). Startup fails closed rather than serving traffic in a misconfigured state. details.kind discriminates which invariant tripped (chain_id_mismatch, router_provenance_mismatch, token_decimals_mismatch, eip712_domain_mismatch, rpc_unreachable). |
internal_error | no | Unhandled exception; represents a bug. correlation_id is essential for support. |
501 1
| Code | Retry | When it occurs |
|---|---|---|
not_implemented | no | Endpoint or capability not implemented yet (Phase 1.5+ feature). |
502 5
| Code | Retry | When it occurs |
|---|---|---|
coinbase_session_creation_failed | yes (header) | Coinbase Onramp/Offramp session creation failed (non-2xx response or rejected request shape). Retry per Retry-After header. |
email_domain_provider_error | yes (immediate) | The email provider (Resend) returned an unexpected error while managing a sending domain (transient outage or an unclassified failure). The raw provider error is wrapped so it never escapes the adapter. |
integration_gateway_error | yes (immediate) | An outbound call to a tenant's Integration Gateway failed, a non-2xx response or a transport error (connection refused, TLS failure, timeout). details.operation names the contract op; details.upstream_status carries the gateway HTTP status (or null for a transport failure). The on-ramp/off-ramp orchestration decides retry vs compensation. |
member_auth_upstream_error | yes (header) | XKOVA IDP returned an unexpected error class with no specific mapping. Internal infra failure; transient retries may help. |
stripe_upstream_error | yes (immediate) | the billing provider API returned a 5xx, 429, or network/decode error. Transient; idempotency keys make retries safe. |
503 10
| Code | Retry | When it occurs |
|---|---|---|
billing_not_configured | no | the billing provider billing surface invoked but is unset (degraded mode). Local-dev OK; staging/prod must set the key before any paying tenant onboards. |
core_banking_outage | yes (header) | Core-banking provider is unreachable (outage). Retry after the indicated interval. |
demo_persona_unavailable | no | POST /v1/demo-login is enabled but the fixed demo tenant has not been seeded, so the requested demo persona (operator/member) does not exist. Run make seed-demo. Distinct from demo_login_disabled (feature on, data missing). |
email_domain_provider_unconfigured | yes (immediate) | A tenant tried to manage a custom sending domain but the domains-capable Resend key () is not configured. The send key is least-privilege and cannot manage domains. Not the caller's fault; retryable once ops provisions the key. |
partner_suspended | no | Partner is suspended (operator incident-response action). All tenants under this partner also return 503. |
service_unavailable | yes (header) | Platform-level outage; check status page. |
tenant_suspended | no | Tenant is suspended (billing past due T+60, or operator incident-response action). |
treasury_engine_unavailable | yes (header) | Engine relay returned an unrecoverable error or is unreachable when the API attempted to submit a deploy / owner-mgmt / execute transaction. Existing transfer state is preserved; client may retry. |
verification_not_configured | yes (immediate) | A production workspace started an identity-verification session but its verification policy carries no hosted_flow_url_template, so there is no vendor flow to send the account holder to. Sandbox workspaces fall back to the demonstration verifier instead; production deliberately does not, because routing a real customer through it would clear them without a real check. Set the template on the workspace's verification policy. |
webhook_not_configured | yes (immediate) | An inbound provider webhook receiver cannot verify the callback because of a server-side misconfiguration, the signing secret is unset, or raw request body capture is disabled (main.ts rawBody:true). Not the caller's fault; the provider's natural redelivery succeeds once ops fixes the config. |
504 1
| Code | Retry | When it occurs |
|---|---|---|
core_banking_timeout | yes (header) | Core-banking provider did not respond in time. Retryable. |
Was this helpful?