Referencia de Errores
Catálogo completo de errores de la API v1 y MCP Gateway de Trusteed. Úsalo para escribir retry logic preciso y gestionar fallos de forma elegante.
Formato de Respuesta de Error
REST y MCP fallan de forma distinta, y la diferencia importa al escribir la lógica de reintentos. REST devuelve un código HTTP con un sobre JSON; una herramienta MCP devuelve HTTP 200 con isError dentro del resultado.
// REST — GET/POST /api/v1/...
{
"success": false,
"error": "Mensaje legible por humanos",
"code": "MACHINE_CODE", // presente en algunos endpoints, no en todos
"details": { ... } // opcional
}
// Resultado de herramienta MCP — el estado del transporte es 200
{
"isError": true,
"content": [{ "type": "text", "text": "Mensaje legible por humanos" }],
"structuredContent": {
"error": {
"code": "CART_NOT_MUTABLE",
"remedy": "refresh_state",
"retryable": false,
"scope": "call",
"details": { ... },
"reference": "https://trusteed.xyz/es/developers/errors#CART_NOT_MUTABLE"
}
}
}En REST, error lleva el mensaje y code todavía no es uniforme en todos los endpoints. Decide por el código HTTP. En MCP, structuredContent.error.code es el discriminador estable, y la tabla de abajo se genera desde la misma fuente que emite el servidor.
Errores de Autenticación
Se devuelven cuando falta la API key, es inválida o carece de los permisos necesarios para realizar la acción solicitada.
| Código HTTP | Error | Significado | Acción Recomendada |
|---|---|---|---|
| 401 | unauthorized | API key ausente o inválida | Verifica que el header X-Agent-Api-Key esté presente y con formato correcto (agnt_xxx) |
| 401 | token_expired | El token OAuth ha expirado | Refresca el token con POST /api/v1/oauth/token |
| 403 | forbidden | La clave carece del scope necesario para esta acción | Revisa los scopes de la clave en el Dashboard bajo Agent Keys |
| 403 | store_suspended | El comercio no está disponible por el momento | Elige un merchant diferente del directorio |
Errores de Rate Limit
Los rate limits se aplican por clave y por herramienta. Lee el header X-RateLimit-Reset para saber cuándo se renueva la ventana.
| Código HTTP | Error | Significado | Acción Recomendada |
|---|---|---|---|
| 429 | rate_limit_exceeded | Límite de peticiones por clave alcanzado | Pausa y reintenta tras el timestamp Unix en X-RateLimit-Reset |
Headers de respuesta de rate limit
X-RateLimit-LimitMáximo de peticiones permitidas en la ventana actualX-RateLimit-RemainingPeticiones restantes antes de alcanzar el límiteX-RateLimit-ResetTimestamp Unix (segundos) en que se renueva la ventana
Errores de Lógica de Negocio
Se devuelven cuando una petición es estructuralmente válida pero falla por el estado de la aplicación (p.ej. carrito expirado, tienda no encontrada).
| Código HTTP | Error | Significado | Acción Recomendada | Reintentable |
|---|---|---|---|---|
| 404 | store_not_found | La tienda no existe o está inactiva | Verifica el slug en el directorio de merchants | No |
| 500 | internal_error | Error de servidor inesperado | Reintenta con backoff exponencial (ver Guía de Reintentos abajo) | Sí |
| 503 | service_unavailable | Sobrecarga temporal o mantenimiento | Reintenta después de 30 segundos | Sí |
Errores de Herramientas MCP
Cuando una herramienta MCP falla, el transporte sigue devolviendo 200. El fallo va en el resultado. Decide por structuredContent.error.remedy: es un conjunto cerrado, así que puedes cubrirlo por completo. Los códigos marcados como fondos capturados significan que puede existir ya un cargo; reintentar uno de ésos puede cobrar dos veces al comprador. PAYMENT_OUTCOME_UNKNOWN es el otro código con posible cargo: la petición de cobro llegó al procesador y no se sabe qué hizo, así que el checkout queda retenido: para, no reintentes con ninguna clave ni método de pago, y escala.
| Código | Qué significa | Remedio | ¿Reintentar? | Vale para |
|---|---|---|---|---|
HUMAN_CONFIRMATION_DECLINED | A human was asked and declined. Nothing was changed. Re-asking without new information is not acceptable behaviour. | Para: no reintentes | No | Sólo esta llamada |
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. | Para: no reintentes | No | Sólo esta llamada |
PAYMENT_AMOUNT_MISMATCHPuede haber fondos ya capturados | The captured amount does not match the amount the session authorised. Funds have moved. Do not retry; this needs reconciliation. | Para: no reintentes | No | Sólo esta llamada |
PAYMENT_CAPTURED_FINALIZATION_FAILEDPuede haber fondos ya capturados | 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`. | Para: no reintentes | No | Sólo esta llamada |
PAYMENT_OUTCOME_UNKNOWNPuede haberse cobrado al comprador | The 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). | Para: no reintentes | No | Sólo esta llamada |
AP2_MERCHANT_AUTHORIZATION_MISSING | This 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. | Debe decidirlo una persona | No | Sólo esta llamada |
AP2_STATE_UNREADABLE | The 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. | Debe decidirlo una persona | No | Sólo esta llamada |
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. | Debe decidirlo una persona | No | Sólo esta llamada |
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. | Debe decidirlo una persona | No | Este comercio (cacheable) |
ORDER_NOT_CANCELLABLE | The order has advanced past the point where an agent may cancel it. `details.status` carries the current status. | Debe decidirlo una persona | No | Sólo esta llamada |
RECEIPT_ISSUANCE_OVERDUE | The purchase completed but its signed receipt did not arrive within the expected window. Do not retry; escalate with the checkout session id. | Debe decidirlo una persona | No | Sólo esta llamada |
RETURN_WINDOW_EXPIRED | The merchant's return window has closed for this order. Only a human on the merchant side can override it. | Debe decidirlo una persona | No | Sólo esta llamada |
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. | Corrige los argumentos | No | Sólo esta llamada |
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`. | Corrige los argumentos | No | Sólo esta llamada |
AP2_MANDATE_REJECTED | An 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. | Corrige los argumentos | No | Sólo esta llamada |
CURRENCY_MISMATCH | The requested items do not share a single currency. Split them into one cart per currency. | Corrige los argumentos | No | Sólo esta llamada |
IDENTITY_TOKEN_REJECTED | The supplied identity token failed verification. `details.reason` carries the reason. | Corrige los argumentos | No | Sólo esta llamada |
IDENTITY_TOKEN_STORE_MISMATCH | The identity token is valid but was issued for a different store. Tokens are not portable across merchants. | Corrige los argumentos | No | Sólo esta llamada |
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. | Corrige los argumentos | No | Sólo esta llamada |
INSUFFICIENT_STOCK | The 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. | Corrige los argumentos | No | Sólo esta llamada |
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. | Corrige los argumentos | No | Sólo esta llamada |
MANDATE_AUDIENCE_MISMATCH | The 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. | Corrige los argumentos | No | Sólo esta llamada |
MANDATE_CURRENCY_MISMATCH | The 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. | Corrige los argumentos | No | Sólo esta llamada |
MANDATE_EXPIRED | The 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`. | Corrige los argumentos | No | Sólo esta llamada |
MANDATE_LIMIT_EXCEEDED | The 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`. | Corrige los argumentos | No | Sólo esta llamada |
MANDATE_SUBJECT_MISMATCH | The 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`. | Corrige los argumentos | No | Sólo esta llamada |
MISSING_REQUIRED_ARGUMENT | A required argument was omitted, or a conditionally-required one was omitted for the chosen mode. `details.field` names it. | Corrige los argumentos | No | Sólo esta llamada |
ORDER_NOT_FOUND | No order matches the given id for this store. | Corrige los argumentos | No | Sólo esta llamada |
PAYMENT_METHOD_REQUIRED | This 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. | Corrige los argumentos | No | Sólo esta llamada |
PAYMENT_OPTION_MISMATCH | This 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. | Corrige los argumentos | No | Sólo esta llamada |
PAYMENT_OPTION_UNAVAILABLE | The 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. | Corrige los argumentos | No | Este comercio (cacheable) |
PAYMENT_REQUIRED | Nothing 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`. | Corrige los argumentos | No | Sólo esta llamada |
PRODUCT_NOT_FOUND | No product matches the given id in this store. Re-run discovery rather than retrying the id. | Corrige los argumentos | No | Sólo esta llamada |
QUANTITY_LIMIT_EXCEEDED | A 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`. | Corrige los argumentos | No | Sólo esta llamada |
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. | Corrige los argumentos | No | Este comercio (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. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
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. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
CHECKOUT_SESSION_NOT_FOUND | No checkout session exists for that id, or it has expired. Start from cart creation. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
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. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
CHECKOUT_TOTAL_NOT_RESOLVABLE | The 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. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
MERCHANT_CATALOG_STALE | The 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. | Relee el estado y vuelve a llamar | No | Este comercio (cacheable) |
OPEN_CART_LIMIT_EXCEEDED | The 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. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
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. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
SESSION_INACTIVE | The session is no longer active and accepts no further changes. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
STATE_PREVIEW_REQUIRED | This 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. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
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. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
VERIFICATION_NOT_FOUND | No attribute verification exists for that reference, or it has expired before being polled. Start a new verification. | Relee el estado y vuelve a llamar | No | Sólo esta llamada |
AP2_MANDATE_REQUIRED | This 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. | Prueba otra vía | No | Sólo esta llamada |
AP2_MANDATES_NOT_SUPPORTED | AP2 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`. | Prueba otra vía | No | Sólo esta llamada |
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. | Prueba otra vía | No | Sólo esta llamada |
FEATURE_NOT_ENABLED | The merchant has not enabled this capability. It is a configuration state, not a fault — retrying never flips it. | Prueba otra vía | No | Este comercio (cacheable) |
NOT_ELIGIBLE | The request is valid and the capability is enabled, but this subject does not qualify under the merchant's rules. | Prueba otra vía | No | Sólo esta llamada |
PAYMENT_DECLINED | The payment instrument was declined by the processor. Nothing was captured. Retrying the same instrument reproduces the decline. | Prueba otra vía | No | Sólo esta llamada |
PAYMENT_METHOD_NOT_CONFIGURED | The requested payment rail is not configured for this store. Call get_payment_methods and pick one that is. | Prueba otra vía | No | Este comercio (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. | Prueba otra vía | No | Este comercio (cacheable) |
SHIPPING_COUNTRY_UNSUPPORTED | The 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. | Prueba otra vía | No | Sólo esta llamada |
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. | Prueba otra vía | No | Sólo esta llamada |
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. | Prueba otra vía | No | Este comercio (cacheable) |
TOOL_IN_OTHER_BUCKET | The 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. | Prueba otra vía | No | Este comercio (cacheable) |
UPSTREAM_RESPONSE_INVALID | A dependency answered, but its response failed schema validation. Retrying reproduces it — the upstream contract is broken, not the connection. | Prueba otra vía | No | Sólo esta llamada |
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. | Reintenta la misma llamada | Sí | Sólo esta llamada |
PAYMENT_FAILED_ROLLED_BACK | The 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. | Reintenta la misma llamada | Sí | Sólo esta llamada |
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. | Reintenta la misma llamada | Sí | Sólo esta llamada |
AP2_LOCK_DECISION_FAILED | Whether this checkout requires an AP2 mandate could not be decided, so no session was created (fail-closed). Retry shortly. | Reintenta con espera creciente | Sí | Sólo esta llamada |
AP2_MANDATE_VERIFICATION_UNAVAILABLE | The 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. | Reintenta con espera creciente | Sí | Sólo esta llamada |
AP2_MERCHANT_AUTHORIZATION_UNAVAILABLE | This 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`. | Reintenta con espera creciente | Sí | Sólo esta llamada |
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. | Reintenta con espera creciente | Sí | Sólo esta llamada |
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. | Reintenta con espera creciente | Sí | Sólo esta llamada |
INTERNAL_ERROR | An unexpected fault on our side. Retry with backoff; if it persists, report it with the tool name and arguments. | Reintenta con espera creciente | Sí | Sólo esta llamada |
RECEIPT_NOT_ISSUED | No 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. | Reintenta con espera creciente | Sí | Sólo esta llamada |
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. | Reintenta con espera creciente | Sí | Sólo esta llamada |
UPSTREAM_UNAVAILABLE | A dependency this tool needs is temporarily unreachable. Retry with backoff. | Reintenta con espera creciente | Sí | Sólo esta llamada |
Guía de Reintentos
No todos los errores vale la pena reintentar. Sigue estas reglas para construir integraciones resilientes.
Errores reintentables
429, 500, 503Backoff: 1s → 2s → 4s → 8s (máx. 4 reintentos)
Errores no reintentables
400, 401, 403, 404, 409, 422Corrige la petición antes de reintentar
Estrategia de backoff recomendada
// Backoff exponencial: máximo 4 reintentos
const REINTENTABLES = new Set([429, 500, 503]);
async function llamarConReintento(fn: () => Promise<Response>): Promise<Response> {
let intento = 0;
while (intento <= 4) {
const res = await fn();
if (res.ok || !REINTENTABLES.has(res.status)) return res;
if (res.status === 429) {
const reset = res.headers.get("X-RateLimit-Reset");
const esperaMs = reset ? (Number(reset) * 1000 - Date.now()) : 1000;
await esperar(Math.max(esperaMs, 0));
} else {
await esperar(1000 * 2 ** intento); // 1s → 2s → 4s → 8s
}
intento++;
}
return fn();
}
function esperar(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}Volver a la Documentación
Explora autenticación, herramientas MCP, webhooks y más.
Documentación para Desarrolladores