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.
| 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 |
ORDER_OWNERSHIP_FAILED | The order is not associated with the authenticated customer. Retrying under the same identity is refused, and repeated attempts are recorded. | 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 |
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 |
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 |
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 |
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_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. | 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 |
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 |
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 |
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_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 |
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) |
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) |
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 and the session was rolled back to READY_FOR_PAYMENT. Nothing was captured; retrying with a fresh payment token 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 |
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 |
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