Docs
⌕ ⌘K
Resources / Errors

Errors

Every error returns the same flat envelope. The HTTP status gives the class of problem, code tells you what to do about it, and request_id is what support will ask for. This page is the catalogue, grouped by where you will hit them.

The envelope

{
  "code": "validation_failed",
  "message": "One or more fields are invalid.",
  "status": 422,
  "request_id": "req_7d3c91f4a2",
  "field_errors": [
    {
      "field": "country",
      "code": "invalid_format",
      "message": "Must be an ISO 3166-1 alpha-2 code."
    }
  ]
}
FieldAlways presentNotes
codeYesStable. This is what you branch on.
messageYesHuman-readable and subject to rewording. Never parse it.
statusYesMirrors the HTTP status, so the body is self-contained in logs.
request_idYesLog it. It is the single identifier support needs to trace a failure.
field_errorsNoPresent on validation_failed. An array of {field, code, message}.
detailNoAdditional structured context for some errors.
typeNoPresent on some errors for finer classification.
Branch on the code, show the message, log the request_id

Codes are stable and safe to switch on. Messages are for humans and may be reworded. And when something goes wrong that this page does not explain, request_id is what turns a support conversation into a lookup.

Status classes

StatusClassGeneral handling
400The request itself is wrong — unparseable body, missing body, or nothing to doRead the code — it names the rule. This is not the general field-validation status.
401Authentication failedCheck which credential you sent, and whether it was rotated.
403Authenticated, but not permittedNot retryable as-is. Scope, agent type, or accreditation forbids it.
404No such object, or not yoursDo not distinguish these in your own UI.
409State conflict — the object is not in a state that allows thisRe-read the object. Retrying the same call will fail identically.
422Validation failed — well-formed but invalidfield_errors[] names the fields. Surface each against its own field.
429Rate limitedBack off with jitter.
5xxNustro-side failureRetry idempotent calls with backoff. Never retry a key-issuing call without an Idempotency-Key.
Nustro enforces

Three statuses, three questions. 400 — was the request itself well formed? 422 — were the values valid? 409 — does the object’s current state allow it? Field validation is 422 validation_failed carrying field_errors[] — never a per-field 400. Build form handling against field_errors generically and you will not need a new case each time a field is added.

409 is not transient

It means your view of the object disagrees with Nustro’s. Retry loops on 409 generate load and never succeed — re-read, then decide. The one exception is idempotency_key_processing, below.

Authentication and access

CodeHTTPMeaningWhat to do
invalid_credentials401The credential is wrong, unknown, or revokedCheck the key; it may have been rotated
certificate_required401A runtime call arrived without an agent certificateAgents authenticate with AEAP-Certificate, never a management key
agent_credential_required401A dispute action — filing, conceding, an offer, evidence, escalation — was sent with a platform key or dashboard sessionSign it as the acting agent. A principal acts through your app, which signs as the agent
aeap_verification_failed401The certificate or proof did not verify, or the timestamp is outside the replay windowCheck clock sync; refetch the issuer JWKS if a key rotated
email_not_verified403The account exists but its email is unverifiedComplete email verification
account_suspended403The platform account is suspendedContact support
tier_exceeds_accreditation403The action requires a higher accreditation than the account holdsNot retryable; accreditation must be raised
production_locked409A production action attempted while production is unavailableRe-read your account status (GET /v1/platform)
forbidden403Generic authorisation failure — the caller is not a party to this objectCheck the identifiers in the path

Account and onboarding

CodeHTTPMeaningWhat to do
email_exists409Registration attempted with an address that already has an accountSign in or reset; do not create a second account
email_already_in_use409An email change collided with an existing accountChoose a different address
confirmation_mismatch422A typed confirmation did not match — account closure, for exampleField-level; correctable
current_password_invalid422Password change with the wrong current passwordField-level; correctable
kyb_in_progress409A verification submission is already under reviewWait for the decision rather than resubmitting
verification_already_accredited409Verification submitted for an already accredited accountNo action needed
not_accredited409A production action attempted before accreditationWait for platform.verification_decided
already_promoted409The account is already on productionDo not auto-retry. The key is not returned again; rotate instead
key_not_provisioned409A production key requested before it existsRe-read your account status (GET /v1/platform)
close_blocked_escrow409Closure attempted with funded escrowRelease escrow first
close_blocked_open_disputes409Closure attempted with open disputesCases must resolve before closure

Document upload

CodeHTTPMeaningWhat to do
document_type_invalid422Not a recognised document typeUse a type from the required list
file_too_large422The upload exceeds the size limitCompress or split before retrying
unsupported_file_type422The file format is not acceptedConvert to an accepted format

Principals and agents

CodeHTTPMeaningWhat to do
external_ref_exists409A principal already exists with that external_refDeduplication is by external_ref — re-read rather than creating a duplicate
customer_not_empty409A Customer deleted while it still holds principalsReassign or ungroup them first — a label is never deleted out from under live principals
tenant_principal_field_locked409A field on your auto-created tenant principal is not editable here — including grouping it under a CustomerEdit it in your company profile — one writer, not two
invalid_status409The object is not in a status that permits this action — activating an agent that is not DRAFT, for exampleRe-read the object. The request was valid; the state forbids it
field_not_allowed_for_type422A field was set that does not apply to the agent’s type or actions — authorized_markets on an agent that doesn’t sell, for exampleRemove it. Named in field_errors
invalid_market_format422The market identifier is malformedCheck the jurisdiction-currency format. Named in field_errors
action_not_permitted403Markets configured on an agent that doesn’t sellOnly agents that carry sell have markets to configure
environment_mismatch409Sandbox and production objects were mixed in one operationCheck which key you sent, and the object’s environment
agent_status_not_mutable409The agent’s lifecycle status does not allow this edit — identity and scope are not editable on a REVOKED or TERMINATED agentRe-read the agent. Not retryable as-is
use_activate_endpoint409DRAFT → ACTIVE was attempted through the status patchCall POST /v1/agents/{did}/activate — that transition issues a keypair and certificate, so it has its own endpoint
escrow_constrained_cannot_reactivate409Reactivation attempted while escrow is CONSTRAINEDResolve the escrow deficiency first; the agent cannot return to ACTIVE under-funded
no_changes_requested400An identity or scope patch contained no changesSend only when something changed. This is a request problem, not a validation failure — hence 400
catalog_url_not_permitted422catalog_url was set on an agent that doesn’t sellThe field is for agents that sell only. Do not offer it on agents that only buy
cert_tier_type_mismatch422The requested certificate tier does not fit the agent type — PERSONAL takes provisional or personal, BUSINESS provisional or businessField-level, inside field_errors
authorized_action_type_mismatch422The authorized actions do not fit the agent type — sell on a personal agent, or a business agent with neither purchase nor sellField-level, inside field_errors
verification_tier_insufficient422 / 409The principal’s verified tier is below the agent type’s — Tier 1 for personal, Tier 2 for business. 422 at registration, 409 at activation, environment switch and type upgradeAssert the tier with PUT /v1/principals/{pid}/verification, then retry
principal_type_mismatch422A personal agent registered under a company principalPersonal agents need an individual principal
invalid_type_transition409A type upgrade on an agent that is not personal, or not ACTIVEOnly personal → business exists; business → personal means a new agent
not_an_upgrade409A certificate-tier upgrade to the same or a lower tierTiers only go up; re-read the agent’s cert_tier

Runtime and settlement

CodeHTTPMeaningWhat to do
spend_policy_violation403A ceiling, window, or counterparty floor refused the payment intentNot retryable. The principal must adjust scope
settlement_verification_failed409The on-chain settlement could not be verified against the intent — not found, wrong amount, or wrong networkConfirm the transaction on-chain before facilitating
facilitation_not_found404No facilitation record for this referenceDelivery must follow a successful facilitate call
confirmation_window_closed409Confirmation arrived after the window closed and the task auto-confirmedThe outcome stands; re-read the task

Refunds

CodeHTTPMeaningWhat to do
settlement_verification_failed402The refund transfer could not be read on-chain yet, or does not match the expected sender, recipient, token, amount or finality — detail names the checkNothing was recorded. Wait for finality, or fix the transfer, then report again
not_original_seller403The reporting agent was not the seller of that paymentOnly the original seller agent can report its refund
intent_not_found404No such payment intentCheck the intent_id
intent_not_settled409A refund against a payment that has not settledThere is nothing to refund yet
refund_exceeds_original409Refunds plus dispute payouts on this payment would exceed its original valueRefund only what is left
dispute_open409A dispute is open on this paymentSettle through the dispute — concede or offer — not with a refund
currency_mismatch422The refund is not in the original payment’s currencyRefund in the currency the buyer paid

Reporting the same tx_hash twice is not an error: the existing refund comes back with 200.

Disputes

CodeHTTPMeaningWhat to do
validation_failed422A field is missing or invalid — a reason other than not_delivered, not_as_described, unauthorized or other, or a malformed amountField-level, inside field_errors
not_applicant403The agent is not the buyer of that transaction — filing, or acting on an offerOnly the buyer agent can file, accept or reject
not_respondent403A concession or offer from anyone but the sellerOnly the seller agent can concede or offer
not_a_party403Evidence from an agent that is not a party to the disputeCheck the dispute ID and the signing agent
transaction_not_settled409Filed against a transaction that has not settledRe-read the transaction
dispute_window_expired409The dispute window elapsedNot recoverable; do not offer the form again
final_sale409The transaction settled with a dispute window of zeroNot recoverable. Show final sale before the user tries
dispute_exists409A dispute is already open on that transactionRender the existing case instead of the form
fully_refunded409The seller has refunded the whole paymentNothing is left to dispute
amount_exceeds_unrefunded409The disputed amount is above the transaction amount minus refundsThe one a user can fix themselves — lower the amount
dispute_with_board409A concession or offer after the dispute went to the boardOnly the board decides now
dispute_closed409A concession on a dispute that is already closed or decidedRe-read the dispute
offer_window_closed409An offer outside pre-arbitration, or after the resolution windowThe seller’s chance to settle directly has passed
offer_amount_invalid422The offer is not above zero and below the claimFor the full claim, concede instead
no_pending_offer409Accept or reject with no offer pendingRe-read the dispute’s offers
evidence_window_closed409Evidence submitted after the window closedNot recoverable for this hearing
not_losing_party403An escalation by the side that won the hearing, or by a non-partyOnly the losing side can escalate
escalation_window_closed409The escalation deadline has passedEnforcement has run or is running; re-read the dispute
dispute_final409The outcome is final — two hearings won, or the third decidedNothing more can be filed on it

Idempotency and rate limits

CodeHTTPMeaningWhat to do
idempotency_key_reused422The same key was sent with a different request bodyUse a fresh key for a genuinely different request
idempotency_key_processing409The original request with this key is still in flightRetry with the same key. Do not generate a new one
rate_limited429Too many requestsBack off with jitter
Nustro enforces

idempotency_key_processing is the one 409 you should retry — with the same key. Generating a new key at that moment is how one timed-out request becomes two objects.

Handling patterns

PatternApplies to
Map field_errors to your formEvery 422. Build it once, generically, rather than adding a case per field.
Re-read, then decideEvery 409 except idempotency_key_processing. Fetch the object, reconcile, then proceed differently or explain.
Do not auto-retryalready_promoted, and any failure on a call that returns a one-time secret. A blind retry destroys the credential rather than recovering it.
Translate for principalsspend_policy_violation, final_sale, dispute_window_expired, dispute_open, tier_exceeds_accreditation. Each has a concrete remedy — or a concrete reason there is none. Say which.
Always log request_idEverything. It is the difference between a support ticket and a lookup.