Skip to content
API Reference

Error Reference

Complete error catalog for the Trusteed API v1 and MCP Gateway. Use this to write precise retry logic and handle failures gracefully.

Error Response Format

REST and MCP fail differently, and the difference matters when you write retry logic. REST returns an HTTP status with a JSON envelope; an MCP tool returns HTTP 200 with isError on the result.

// REST — GET/POST /api/v1/...
{
  "success": false,
  "error": "Human-readable message",
  "code": "MACHINE_CODE",   // present on some endpoints, not all
  "details": { ... }        // optional
}

// MCP tool result — transport status is 200
{
  "isError": true,
  "content": [{ "type": "text", "text": "Human-readable message" }],
  "structuredContent": {
    "error": {
      "code": "CART_NOT_MUTABLE",
      "remedy": "refresh_state",
      "retryable": false,
      "scope": "call",
      "details": { ... },
      "reference": "https://trusteed.xyz/en/developers/errors#CART_NOT_MUTABLE"
    }
  }
}

On REST, error carries the message and code is not yet uniform across every endpoint. Branch on the HTTP status. On MCP, structuredContent.error.code is the stable discriminator and the table below is generated from the same source the server emits.

Authentication Errors

Returned when the API key is missing, invalid, or lacks the required permissions to perform the requested action.

HTTP CodeErrorMeaningRecommended Action
401unauthorizedMissing or invalid API keyVerify the X-Agent-Api-Key header is present and correctly formatted (agnt_xxx)
401token_expiredOAuth token has expiredRefresh the token via POST /api/v1/oauth/token
403forbiddenKey lacks the required scope for this actionCheck key scopes in the Dashboard under Agent Keys
403store_suspendedMerchant is not currently availableChoose a different merchant from the directory

Rate Limit Errors

Rate limits are enforced per key and per tool. Read the X-RateLimit-Reset header to know when the window resets.

HTTP CodeErrorMeaningRecommended Action
429rate_limit_exceededPer-key request limit hitPause and retry after the Unix timestamp in X-RateLimit-Reset

Rate limit response headers

  • X-RateLimit-LimitMaximum requests allowed in the current window
  • X-RateLimit-RemainingRequests remaining before the limit is hit
  • X-RateLimit-ResetUnix timestamp (seconds) when the window resets

Business Logic Errors

Returned when a request is structurally valid but fails due to application state (e.g. cart expired, store not found).

HTTP CodeErrorMeaningRecommended ActionRetryable
404store_not_foundStore does not exist or is inactiveVerify the slug from the merchant directoryNo
500internal_errorUnexpected server errorRetry with exponential backoff (see Retry Guidance below)Yes
503service_unavailableTemporary overload or maintenanceRetry after 30 secondsYes

MCP Tool Errors

When an MCP tool fails, the transport still returns 200. The failure is on the result. Branch on structuredContent.error.remedy: it is a closed set, so you can handle it exhaustively. Codes marked funds captured mean a charge may already exist; retrying one of those can charge the buyer twice. PAYMENT_OUTCOME_UNKNOWN is the other possible-charge code: the charge request reached the processor and nobody knows what it did, so the checkout is held: stop, do not retry with any key or payment method, and escalate.

CodeWhat it meansRemedyRetry?Holds for
HUMAN_CONFIRMATION_DECLINEDA human was asked and declined. Nothing was changed. Re-asking without new information is not acceptable behaviour.Stop: do not retryNoThis call only
IDEMPOTENCY_KEY_CONFLICTThis session is already bound to a different idempotency_key. Reusing the session under a new key is refused because it cannot be distinguished from a double-submit.Stop: do not retryNoThis call only
PAYMENT_AMOUNT_MISMATCHFunds may already be capturedThe captured amount does not match the amount the session authorised. Funds have moved. Do not retry; this needs reconciliation.Stop: do not retryNoThis call only
PAYMENT_CAPTURED_FINALIZATION_FAILEDFunds may already be capturedThe capture SUCCEEDED and order finalization then failed. The buyer has been charged. Do not retry — a retry risks a second charge. Escalate with `details.capture_reference`.Stop: do not retryNoThis call only
PAYMENT_OUTCOME_UNKNOWNBuyer may have been chargedThe charge request reached the payment processor and its outcome is unknown: the buyer MAY have been charged. Do not retry with this or any other key or payment method — the checkout session is held until the attempt is reconciled by the merchant. Escalate with the details (`rail`, and `paypal_order_id` when present).Stop: do not retryNoThis call only
AP2_MERCHANT_AUTHORIZATION_MISSINGThis checkout session is locked to AP2 and the merchant could not sign the checkout (`ap2.merchant_authorization`; UCP error `merchant_authorization_missing`), so no unsigned checkout is returned. Nothing was charged. Retrying reproduces it until the merchant signing key is fixed; report the checkout id.A human must decideNoThis call only
AP2_STATE_UNREADABLEThe AP2 state stored on this checkout session could not be read, so the session was not completed (fail-closed) and nothing was charged. Retrying reproduces it; report the checkout id.A human must decideNoThis call only
HUMAN_CONFIRMATION_REQUIREDThe action needs a human to approve it and this path cannot ask one — the client does not support elicitation, or the approver is the merchant rather than the buyer. `details.approver` says who. Nothing was changed.A human must decideNoThis call only
NATIVE_CHECKOUT_REQUIREDThis merchant requires the purchase to complete on the platform's own hosted checkout. `details.checkout_url` carries where to send the buyer.A human must decideNoThis merchant (cacheable)
ORDER_NOT_CANCELLABLEThe order has advanced past the point where an agent may cancel it. `details.status` carries the current status.A human must decideNoThis call only
RECEIPT_ISSUANCE_OVERDUEThe purchase completed but its signed receipt did not arrive within the expected window. Do not retry; escalate with the checkout session id.A human must decideNoThis call only
RETURN_WINDOW_EXPIREDThe merchant's return window has closed for this order. Only a human on the merchant side can override it.A human must decideNoThis call only
AGENT_TOKEN_INVALIDThe supplied agent token failed verification. `details.reason` carries the verifier's reason. Re-mint the token; do not retry the same one.Fix the argumentsNoThis call only
AP2_INVALID_PLATFORM_PROFILE`meta['ucp-agent'].profile` is not an https URL without credentials (max 2048 characters). Nothing was changed. Send a valid platform profile URL or omit `meta`.Fix the argumentsNoThis call only
AP2_MANDATE_REJECTEDAn AP2 mandate presented to `ucp_complete_checkout` did not verify or is not the one that settled the session; this call charges nothing and changes nothing (on a session that was already completed, that settlement happened in an earlier call). `details.ucp_code` carries the normative UCP error (`agent_missing_key`, `mandate_invalid_signature`, `mandate_expired`, `mandate_scope_mismatch`, `merchant_authorization_invalid`, `merchant_authorization_missing`), `details.reason` a stable identifier and `details.next_step` what to do: for `mandate_scope_mismatch` read the checkout again with `ucp_get_checkout` and have the user sign new mandates over exactly that checkout; for `mandate_expired` sign again.Fix the argumentsNoThis call only
CURRENCY_MISMATCHThe requested items do not share a single currency. Split them into one cart per currency.Fix the argumentsNoThis call only
IDENTITY_TOKEN_REJECTEDThe supplied identity token failed verification. `details.reason` carries the reason.Fix the argumentsNoThis call only
IDENTITY_TOKEN_STORE_MISMATCHThe identity token is valid but was issued for a different store. Tokens are not portable across merchants.Fix the argumentsNoThis call only
INSUFFICIENT_INPUTThe call is well-formed but does not carry enough material to act on — e.g. a comparison with fewer than two resolvable products.Fix the argumentsNoThis call only
INSUFFICIENT_STOCKThe store catalog reports fewer units available than the line item asks for. `details` carries `requested_quantity`, `available_quantity` and `source` — the latter says which reading was compared (`catalog_snapshot` is the replicated catalog, which may lag the platform, not a live inventory call). Reduce the quantity; retrying the identical call will not change the answer.Fix the argumentsNoThis call only
INVALID_ARGUMENTAn argument is malformed — a non-UUID id, an unparseable product id, or a value outside the accepted range. `details.field` names it when known.Fix the argumentsNoThis call only
MANDATE_AUDIENCE_MISMATCHThe payment mandate was issued for a different store: its `aud` does not name this one. Mandates are not portable across merchants. The cart is not the reason — present a mandate whose `aud` is this store.Fix the argumentsNoThis call only
MANDATE_CURRENCY_MISMATCHThe payment mandate authorises spending in a currency other than the one this cart is priced in, so its `max_amount_cents` cannot be compared against the cart total at all. The cart is not the reason — present a mandate denominated in the cart's currency. `details.currency` is the MANDATE's currency.Fix the argumentsNoThis call only
MANDATE_EXPIREDThe payment mandate presented had already expired (`exp` in the past). The cart is not the reason and reducing it changes nothing — present a mandate with a later `exp`. `details.remediation` is `renew_mandate`.Fix the argumentsNoThis call only
MANDATE_LIMIT_EXCEEDEDThe cart total exceeds the `max_amount_cents` of the payment mandate the caller itself presented. `details` carries that limit and the cart total, because both are already known to the caller. Reduce the cart (or present a mandate that covers it); the identical call cannot succeed. The mandate's other boundaries have their own codes — `MANDATE_EXPIRED`, `MANDATE_AUDIENCE_MISMATCH`, `MANDATE_CURRENCY_MISMATCH`.Fix the argumentsNoThis call only
MANDATE_SUBJECT_MISMATCHThe payment mandate's `sub` is not the identity this call authenticated as. A mandate is a spending cap the caller declares for itself, so `sub` must name the caller exactly as the server identifies it; `details.expected_sub` carries that value. The cart is not the reason — present the same mandate with `sub` set to `details.expected_sub`.Fix the argumentsNoThis call only
MISSING_REQUIRED_ARGUMENTA required argument was omitted, or a conditionally-required one was omitted for the chosen mode. `details.field` names it.Fix the argumentsNoThis call only
ORDER_NOT_FOUNDNo order matches the given id for this store.Fix the argumentsNoThis call only
PAYMENT_METHOD_REQUIREDThis store requires the buyer's chosen payment method before completing the purchase. Ask the person which option they want from `details.payment_options`, then call again with `payment_option` set to their choice. Do not choose for them.Fix the argumentsNoThis call only
PAYMENT_OPTION_MISMATCHThis checkout already has a saved `payment_option` from a previous call and this call asked for a different one. Retry with the saved option (`details.approved_option`), or start a new checkout if the person wants a different method.Fix the argumentsNoThis call only
PAYMENT_OPTION_UNAVAILABLEThe requested `payment_option` is not available for this store. Ask the person to choose one of the available options in `details.payment_options`; this option will not become available on retry.Fix the argumentsNoThis merchant (cacheable)
PAYMENT_REQUIREDNothing was charged: the checkout needs a signed payment. `details.x402_offer` carries the server-computed offer (network, payTo, asset, atomic amount). Sign exactly that and call again with `x402_payload`.Fix the argumentsNoThis call only
PRODUCT_NOT_FOUNDNo product matches the given id in this store. Re-run discovery rather than retrying the id.Fix the argumentsNoThis call only
QUANTITY_LIMIT_EXCEEDEDA line item asks for more units than a single cart line accepts. `details` carries `requested_quantity` and `maximum_quantity`, and `details.field` names the line. Split the order or reduce the quantity; the identical call cannot succeed. This is the protocol's structural ceiling — a merchant rule capping quantities blocks earlier and reports as `CHECKOUT_BLOCKED_BY_POLICY`.Fix the argumentsNoThis call only
TOOL_NOT_FOUNDNo tool is registered under that name for this store. The set of tools a store exposes is fixed for the session — re-run discovery rather than guessing another name.Fix the argumentsNoThis merchant (cacheable)
CART_NOT_FOUNDNo cart session exists for that id, or it has expired. Create a new cart; do not retry with the same id.Re-read state, then call againNoThis call only
CART_NOT_MUTABLEThe cart was modified, completed or expired since it was read. Re-read the cart state and reapply the change against the current version.Re-read state, then call againNoThis call only
CHECKOUT_SESSION_NOT_FOUNDNo checkout session exists for that id, or it has expired. Start from cart creation.Re-read state, then call againNoThis call only
CHECKOUT_STATUS_INVALIDThe operation is not legal from the session's current status. `details.status` carries that status; advance the session through the documented transition first.Re-read state, then call againNoThis call only
CHECKOUT_TOTAL_NOT_RESOLVABLEThe session carries no readable total, so there is no amount to charge. This is NOT a free purchase: a missing total used to read as zero and skip the human-approval gate, completing an order that nobody paid. Re-price the cart (select shipping, apply discounts) so the session carries a `total` entry with `amount_cents`, then call again.Re-read state, then call againNoThis call only
MERCHANT_CATALOG_STALEThe merchant's catalogue has not been synchronised recently enough to verify the approved price against it, so the checkout was refused before any charge. `details` carries `age_seconds` and `max_age_seconds`. Nothing was charged. Retrying now will not help: the merchant must restore catalogue synchronisation first; once it is fresh, the same idempotency key may be reused.Re-read state, then call againNoThis merchant (cacheable)
OPEN_CART_LIMIT_EXCEEDEDThe agent already holds the maximum number of open carts in this store. `details` carries `open_carts`, `max_open_carts` and `retry_after_seconds` (until the oldest open cart expires). Complete or cancel an open cart, resume one with its `agent_session_id`, or wait; the identical call cannot succeed until a slot frees up.Re-read state, then call againNoThis call only
PRECONDITION_NOT_META prerequisite step has not been completed — no shipping method selected, no shipping options loaded yet. `details.required_step` names the tool to call first; retrying this one without it repeats the same refusal.Re-read state, then call againNoThis call only
SESSION_INACTIVEThe session is no longer active and accepts no further changes.Re-read state, then call againNoThis call only
STATE_PREVIEW_REQUIREDThis checkout could not proceed against a reconfirmed merchant state: the session could not be re-priced to it, its total changed after it was evaluated, or the reconfirmation no longer holds. `details.reason` says which. Nothing was charged. Call preview_checkout again to approve the current state, then complete_checkout — the same idempotency key may be reused.Re-read state, then call againNoThis call only
STATE_RECONFIRMATION_REQUIREDThe merchant's own state moved between the approved preview and this execution, by more than the tolerance but not past the blocking cut. `details.reconfirm_state_hash` is the hash of the state now in force; call again passing it as `reconfirmed_state_hash` to execute against THAT state. A retry without it, or with a stale hash, is refused again.Re-read state, then call againNoThis call only
VERIFICATION_NOT_FOUNDNo attribute verification exists for that reference, or it has expired before being polled. Start a new verification.Re-read state, then call againNoThis call only
AP2_MANDATE_REQUIREDThis checkout session is locked to AP2 (UCP `mandate_required`): `ucp_complete_checkout` needs `ap2.checkout_mandate` and the Payment Mandate in `payment.instruments[].credential.token`, and at least one is missing (`details.missing`). Nothing was charged.Try a different pathNoThis call only
AP2_MANDATES_NOT_SUPPORTEDAP2 mandates (`ap2.checkout_mandate`) cannot be used on this checkout (AP2 `mandates_not_supported`): the session was not negotiated with AP2, this endpoint cannot verify AP2 mandates, or this store has no settlement rail for them (AP2 settles only through the test payment options: card or x402 on Base Sepolia). Nothing was charged. Complete the checkout without mandates, or create a new session announcing a platform profile that advertises `dev.ucp.common.payment.ap2_mandate`.Try a different pathNoThis call only
CHECKOUT_BLOCKED_BY_POLICYA merchant enforcement rule blocked this checkout. `details.rule_code` names the rule. The block follows from THIS order — a different amount, item set or destination may pass — so retrying the identical checkout will not.Try a different pathNoThis call only
FEATURE_NOT_ENABLEDThe merchant has not enabled this capability. It is a configuration state, not a fault — retrying never flips it.Try a different pathNoThis merchant (cacheable)
NOT_ELIGIBLEThe request is valid and the capability is enabled, but this subject does not qualify under the merchant's rules.Try a different pathNoThis call only
PAYMENT_DECLINEDThe payment instrument was declined by the processor. Nothing was captured. Retrying the same instrument reproduces the decline.Try a different pathNoThis call only
PAYMENT_METHOD_NOT_CONFIGUREDThe requested payment rail is not configured for this store. Call get_payment_methods and pick one that is.Try a different pathNoThis merchant (cacheable)
PLAN_UPGRADE_REQUIREDThe merchant's plan does not include this path. Only the merchant can change that; pick another payment method or tool.Try a different pathNoThis merchant (cacheable)
SHIPPING_COUNTRY_UNSUPPORTEDThe merchant's enforced rules restrict shipping to a list of countries and the requested destination is not on it, so no rate is offered — an estimated rate towards an unserved country reads as usable and is not. `details` carries `requested_country`, `allowed_countries` and `derived_from` (the rules the list comes from). Retry with a destination on the list; nothing about the cart was changed.Try a different pathNoThis call only
STATE_EXECUTION_BLOCKEDThe merchant's own state diverged from the approved preview past the point where reconfirming is acceptable — insufficient stock, a price move too large, or a policy version change under a prior approval. `details.reasons` says which. No reconfirmation is offered: a different item set or quantity may pass.Try a different pathNoThis call only
STORE_NOT_CONFIGUREDThis store has not completed the configuration this tool depends on. Retrying will not change that; another store or another tool may work.Try a different pathNoThis merchant (cacheable)
TOOL_IN_OTHER_BUCKETThe tool exists for this store but is served by a different MCP bucket endpoint than the one this session is connected to. The name is right; call it on the bucket named in the message. Its schema is not introspectable from here.Try a different pathNoThis merchant (cacheable)
UPSTREAM_RESPONSE_INVALIDA dependency answered, but its response failed schema validation. Retrying reproduces it — the upstream contract is broken, not the connection.Try a different pathNoThis call only
CHECKOUT_ALREADY_IN_PROGRESSA checkout for this session is already running or has completed. Retrying with the SAME idempotency_key is safe and returns the existing order.Retry the same callYesThis call only
PAYMENT_FAILED_ROLLED_BACKThe payment attempt failed BEFORE any charge was made — the processor was never asked to charge, or it rejected the request — and the session was rolled back to READY_FOR_PAYMENT. Nothing was captured; retrying (with a fresh payment token for KYApay) is safe.Retry the same callYesThis call only
PAYMENT_NOT_APPROVEDThe payer did not approve the payment. Nothing was captured and the session was rolled back to READY_FOR_PAYMENT — a fresh approval attempt is safe.Retry the same callYesThis call only
AP2_LOCK_DECISION_FAILEDWhether this checkout requires an AP2 mandate could not be decided, so no session was created (fail-closed). Retry shortly.Retry with backoffYesThis call only
AP2_MANDATE_VERIFICATION_UNAVAILABLEThe AP2 mandates could not be checked right now (the platform profile that holds the trust anchor could not be fetched). Nothing was charged. Retry shortly with the same mandates.Retry with backoffYesThis call only
AP2_MERCHANT_AUTHORIZATION_UNAVAILABLEThis checkout session is locked to AP2 and its response could not be signed right now because the session changed while it was being signed (UCP error `merchant_authorization_missing`, transient). No unsigned checkout is returned and nothing was charged. Read the checkout again with `ucp_get_checkout`.Retry with backoffYesThis call only
CART_ATTRIBUTE_WRITE_FAILEDAn attribute the enforcement layer depends on could not be written to the platform cart, so the step was refused rather than leaving a rule unable to fire. Retry shortly.Retry with backoffYesThis call only
ENFORCEMENT_UNAVAILABLEThe enforcement layer could not be consulted — the signed policy snapshot was unreadable, or the evaluator itself errored. The operation was refused fail-closed rather than proceeding unprotected. Retry shortly.Retry with backoffYesThis call only
INTERNAL_ERRORAn unexpected fault on our side. Retry with backoff; if it persists, report it with the tool name and arguments.Retry with backoffYesThis call only
RECEIPT_NOT_ISSUEDNo receipt for this session yet. If complete_checkout was called seconds ago, its receipt may still be queued: fetch again shortly. If it was not called, there is nothing to fetch yet; do not create a new cart.Retry with backoffYesThis call only
UPSTREAM_ERRORThe merchant's commerce platform returned an error for this read or write. Retry with backoff; if it persists the merchant's platform is at fault, not the request.Retry with backoffYesThis call only
UPSTREAM_UNAVAILABLEA dependency this tool needs is temporarily unreachable. Retry with backoff.Retry with backoffYesThis call only

Retry Guidance

Not all errors are worth retrying. Follow these rules to build resilient integrations.

Retryable errors

429, 500, 503

Backoff: 1s → 2s → 4s → 8s (max 4 retries)

Non-retryable errors

400, 401, 403, 404, 409, 422

Fix the request before retrying

Recommended backoff strategy

// Exponential backoff: max 4 retries
const RETRYABLE = new Set([429, 500, 503]);

async function callWithRetry(fn: () => Promise<Response>): Promise<Response> {
  let attempt = 0;
  while (attempt <= 4) {
    const res = await fn();
    if (res.ok || !RETRYABLE.has(res.status)) return res;

    if (res.status === 429) {
      const reset = res.headers.get("X-RateLimit-Reset");
      const waitMs = reset ? (Number(reset) * 1000 - Date.now()) : 1000;
      await sleep(Math.max(waitMs, 0));
    } else {
      await sleep(1000 * 2 ** attempt); // 1s → 2s → 4s → 8s
    }
    attempt++;
  }
  return fn();
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

Back to Developer Docs

Explore authentication, MCP tools, webhooks, and more.

Developer Documentation
API Error Reference | Trusteed Developers