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
All errors follow a consistent JSON envelope. Parse the error field to identify the error type programmatically.
{
"error": "error_code",
"message": "Human-readable message",
"details": { ... }
}The error field is stable across API versions — build retry logic against it, not against the message string.
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 | tier_required | Feature requires a higher subscription tier | Upgrade the plan from the Dashboard |
| 403 | store_suspended | Merchant trust_score is below 0.20 | Choose a different merchant — only proceed with trust_score ≥ 0.70 |
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 |
| 429 | tool_limit_exceeded | Per-tool limit exceeded (e.g. search_products: 20/min) | Reduce search frequency or batch requests |
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 |
|---|---|---|---|---|
| 400 | invalid_slug | Store slug format is invalid | Use alphanumeric characters and hyphens only (e.g. my-store) | No |
| 404 | store_not_found | Store does not exist or is inactive | Verify the slug from the merchant directory | No |
| 404 | product_not_found | Product is unavailable or delisted | Call search_products again to get an updated product list | No |
| 409 | cart_expired | Cart is older than 30 minutes | Create a new cart with create_cart | No |
| 422 | checkout_not_ready | Cart is empty or missing required fields | Add items before calling complete_checkout | 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 |
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));
}