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 Code | Error | Meaning | Recommended Action |
|---|---|---|---|
| 401 | unauthorized | Missing or invalid API key | Verify the X-Agent-Api-Key header is present and correctly formatted (agnt_xxx) |
| 401 | token_expired | OAuth token has expired | Refresh the token via POST /api/v1/oauth/token |
| 403 | forbidden | Key lacks the required scope for this action | Check key scopes in the Dashboard under Agent Keys |
| 403 | store_suspended | Merchant is not currently available | Choose 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 Code | Error | Meaning | Recommended Action |
|---|---|---|---|
| 429 | rate_limit_exceeded | Per-key request limit hit | Pause and retry after the Unix timestamp in X-RateLimit-Reset |
Rate limit response headers
X-RateLimit-LimitMaximum requests allowed in the current windowX-RateLimit-RemainingRequests remaining before the limit is hitX-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 Code | Error | Meaning | Recommended Action | Retryable |
|---|---|---|---|---|
| 404 | store_not_found | Store does not exist or is inactive | Verify the slug from the merchant directory | No |
| 500 | internal_error | Unexpected server error | Retry with exponential backoff (see Retry Guidance below) | Yes |
| 503 | service_unavailable | Temporary overload or maintenance | Retry after 30 seconds | Yes |
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.
| Code | What it means | Remedy | Retry? | Holds for |
|---|---|---|---|---|
HUMAN_CONFIRMATION_DECLINED | A human was asked and declined. Nothing was changed. Re-asking without new information is not acceptable behaviour. | Stop — do not retry | No | This call only |
IDEMPOTENCY_KEY_CONFLICT | This 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 retry | No | This call only |
ORDER_OWNERSHIP_FAILED | The order is not associated with the authenticated customer. Retrying under the same identity is refused, and repeated attempts are recorded. | Stop — do not retry | No | This call only |
PAYMENT_AMOUNT_MISMATCHFunds may already be captured | The captured amount does not match the amount the session authorised. Funds have moved. Do not retry; this needs reconciliation. | Stop — do not retry | No | This call only |
PAYMENT_CAPTURED_FINALIZATION_FAILEDFunds may already be captured | The 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 retry | No | This call only |
HUMAN_CONFIRMATION_REQUIRED | The 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 decide | No | This call only |
NATIVE_CHECKOUT_REQUIRED | This 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 decide | No | This merchant (cacheable) |
ORDER_NOT_CANCELLABLE | The order has advanced past the point where an agent may cancel it. `details.status` carries the current status. | A human must decide | No | This call only |
RETURN_WINDOW_EXPIRED | The merchant's return window has closed for this order. Only a human on the merchant side can override it. | A human must decide | No | This call only |
AGENT_TOKEN_INVALID | The supplied agent token failed verification. `details.reason` carries the verifier's reason. Re-mint the token; do not retry the same one. | Fix the arguments | No | This call only |
CURRENCY_MISMATCH | The requested items do not share a single currency. Split them into one cart per currency. | Fix the arguments | No | This call only |
IDENTITY_TOKEN_REJECTED | The supplied identity token failed verification. `details.reason` carries the reason. | Fix the arguments | No | This call only |
IDENTITY_TOKEN_STORE_MISMATCH | The identity token is valid but was issued for a different store. Tokens are not portable across merchants. | Fix the arguments | No | This call only |
INSUFFICIENT_INPUT | The 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 arguments | No | This call only |
INVALID_ARGUMENT | An 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 arguments | No | This call only |
MANDATE_LIMIT_EXCEEDED | The order exceeds a boundary of the payment mandate the caller itself presented — amount, currency, audience or expiry. `details` carries the mandate's own limit and the cart total, because both are already known to the caller. Correct the cart (or present a mandate that covers it) and call again; the identical call cannot succeed. | Fix the arguments | No | This call only |
MISSING_REQUIRED_ARGUMENT | A required argument was omitted, or a conditionally-required one was omitted for the chosen mode. `details.field` names it. | Fix the arguments | No | This call only |
ORDER_NOT_FOUND | No order matches the given id for this store. | Fix the arguments | No | This call only |
PRODUCT_NOT_FOUND | No product matches the given id in this store. Re-run discovery rather than retrying the id. | Fix the arguments | No | This call only |
TOOL_NOT_FOUND | No 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 arguments | No | This merchant (cacheable) |
CART_NOT_FOUND | No 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 again | No | This call only |
CART_NOT_MUTABLE | The 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 again | No | This call only |
CHECKOUT_SESSION_NOT_FOUND | No checkout session exists for that id, or it has expired. Start from cart creation. | Re-read state, then call again | No | This call only |
CHECKOUT_STATUS_INVALID | The 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 again | No | This call only |
PRECONDITION_NOT_MET | A 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 again | No | This call only |
SESSION_INACTIVE | The session is no longer active and accepts no further changes. | Re-read state, then call again | No | This call only |
STATE_RECONFIRMATION_REQUIRED | The 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 again | No | This call only |
VERIFICATION_NOT_FOUND | No attribute verification exists for that reference, or it has expired before being polled. Start a new verification. | Re-read state, then call again | No | This call only |
CHECKOUT_BLOCKED_BY_POLICY | A 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 path | No | This call only |
FEATURE_NOT_ENABLED | The merchant has not enabled this capability. It is a configuration state, not a fault — retrying never flips it. | Try a different path | No | This merchant (cacheable) |
NOT_ELIGIBLE | The request is valid and the capability is enabled, but this subject does not qualify under the merchant's rules. | Try a different path | No | This call only |
PAYMENT_DECLINED | The payment instrument was declined by the processor. Nothing was captured. Retrying the same instrument reproduces the decline. | Try a different path | No | This call only |
PAYMENT_METHOD_NOT_CONFIGURED | The requested payment rail is not configured for this store. Call get_payment_methods and pick one that is. | Try a different path | No | This merchant (cacheable) |
PLAN_UPGRADE_REQUIRED | The merchant's plan does not include this path. Only the merchant can change that; pick another payment method or tool. | Try a different path | No | This merchant (cacheable) |
STATE_EXECUTION_BLOCKED | The 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 path | No | This call only |
STORE_NOT_CONFIGURED | This 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 path | No | This merchant (cacheable) |
UPSTREAM_RESPONSE_INVALID | A dependency answered, but its response failed schema validation. Retrying reproduces it — the upstream contract is broken, not the connection. | Try a different path | No | This call only |
CHECKOUT_ALREADY_IN_PROGRESS | A 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 call | Yes | This call only |
PAYMENT_FAILED_ROLLED_BACK | The payment attempt failed and the session was rolled back to READY_FOR_PAYMENT. Nothing was captured; retrying with a fresh payment token is safe. | Retry the same call | Yes | This call only |
PAYMENT_NOT_APPROVED | The 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 call | Yes | This call only |
CART_ATTRIBUTE_WRITE_FAILED | An 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 backoff | Yes | This call only |
ENFORCEMENT_UNAVAILABLE | The 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 backoff | Yes | This call only |
INTERNAL_ERROR | An unexpected fault on our side. Retry with backoff; if it persists, report it with the tool name and arguments. | Retry with backoff | Yes | This call only |
UPSTREAM_ERROR | The 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 backoff | Yes | This call only |
UPSTREAM_UNAVAILABLE | A dependency this tool needs is temporarily unreachable. Retry with backoff. | Retry with backoff | Yes | This call only |
Retry Guidance
Not all errors are worth retrying. Follow these rules to build resilient integrations.
Retryable errors
429, 500, 503Backoff: 1s → 2s → 4s → 8s (max 4 retries)
Non-retryable errors
400, 401, 403, 404, 409, 422Fix 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));
}