Skip to content

Guide

Errors

Every error on a /v1 path, unknown paths included, uses one JSON body, and the HTTP status is the real status. Branch on the code, not the message.
On this page

The error body#

400 invalid_parameter

{
  "error": {
    "code": "invalid_parameter",
    "message": "One or more parameters are invalid.",
    "request_id": "0199f0c2-5a1e-7c3b-9d4e-2b6f8a1c3d5e",
    "details": [ { "param": "limit", "message": "Must be at most 500." } ]
  }
}

429 rate_limited

{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit reached: at most 250 requests per minute per account.",
    "request_id": "0199f0c2-5a1e-7c3b-9d4e-2b6f8a1c3d5e",
    "retry_after_ms": 4200
  }
}

Error object

  • errorobjectrequired7 fields inside
    • codestringrequiredStable machine-readable error code, for example invalid_parameter, rate_limited, data_unavailable.
    • detailsarray<object>2 fields inside
      • messagestringrequired
      • paramstringrequired
    • messagestringrequiredHuman-readable explanation.
    • request_idstringrequiredQuote this when contacting support.
    • retry_after_msintegerMilliseconds to wait before retrying, when the failure is retryable.
  • retry_after_ms and the Retry-After header (whole seconds, rounded up) appear when waiting can help.
  • details appears on parameter errors and names every offending parameter.
  • Messages are written for people and may change. Branch on `code`. Error text never contains internals of the services behind the API.
  • The X-Request-ID response header is always present. Quote it when you contact support.
  • The export endpoints' errors may also carry a retry_after field in seconds, and export_not_ready has no request_id in its body.
  • The MCP endpoint reports protocol-level failures as JSON-RPC errors and endpoint failures as tool errors holding this body.

Error codes#

StatusCodeMeaning and what to do
400invalid_parameterA value is malformed or out of range. details names the parameters.
400unknown_parameter, duplicate_parameter, missing_parameterSee Conventions.
400query_too_largeMore than 2,048 bytes or 32 parameters in the query.
400api_key_in_queryA key was sent in the URL. Use a header.
400invalid_cursorThe cursor is invalid, expired, or belongs to a different query or account.
400invalid_bodyThe JSON body of an export request is not valid.
401api_key_required, invalid_api_keyNo key, or an unknown or revoked key.
401invalid_project, account_unavailableThe key's project is inactive or the account is unavailable.
403api_subscription_requiredThe account has no active API subscription.
403dataset_release_restrictedThe dataset this endpoint belongs to is not enabled for API keys.
403overnight_requiredOvernight (gth) data needs API Pro.
403history_limit_exceeded, history_not_in_planThe requested times reach back further than your plan's history window.
403streaming_not_in_planWebSockets need API Pro.
403account_suspendedThe account is suspended. Contact support.
404not_foundUnknown path or resource, or an export that does not exist.
404export_not_readyThe export has no file yet.
405method_not_allowedUse GET (POST only on /mcp and /v1/exports).
409stream_connection_limitYour account already has a connection on this WebSocket stream.
413range_too_largeThe window or result is larger than the endpoint allows. Narrow it.
422data_unavailableThe request is valid but there is no data for it: a holiday, an expiration that is not listed, a time before retention begins.
426upgrade_requiredA WebSocket endpoint was called without an upgrade.
429rate_limited, burst_limitedOver the per-minute or per-second limit, or too many concurrent requests. Wait Retry-After.
429cooldownTemporarily blocked after abusive behaviour. Wait Retry-After.
429export_limitFive exports are already queued or running.
500internal_errorA fault on our side. Retry, and quote request_id if it persists.
503upstream_busyThe data service is at capacity. Retry after Retry-After.
503upstream_unavailableThis data is temporarily unavailable, or is not part of the deployment's data entitlements. Retry later.
503data_pendingThe requested range is being prepared. Retry after Retry-After. This is how a first request for a not-yet-loaded session is answered.
503capacity, stream_unavailable, account_state_unavailableService capacity, a stream that is not running, or account state that could not be read. Retry after Retry-After.
504upstream_timeoutThe request exceeded its 20-second budget. Narrow the range or retry.

Retrying#

Retry 429, 500, 503 and 504 with exponential backoff and jitter, honouring Retry-After when present. Do not retry other 4xx errors unchanged: they will fail the same way. A GET is always safe to retry.

Retry with backoff

async function getJson(url, attempts = 5) {
  for (let i = 0; ; i++) {
    const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.ORDERMAP_API_KEY}` } });
    if (res.ok) return res.json();
    const retryable = [429, 500, 503, 504].includes(res.status);
    if (!retryable || i === attempts - 1) throw new Error(`${res.status} ${await res.text()}`);
    // Honour Retry-After (whole seconds) when present, else back off exponentially with jitter.
    const waitMs = Number(res.headers.get("retry-after")) * 1000 || 2 ** i * 500 + Math.random() * 250;
    await new Promise((resolve) => setTimeout(resolve, waitMs));
  }
}

Errors on WebSockets#

Errors before the upgrade are ordinary JSON errors from the table above. After the upgrade a refused request is an error message, and a closed connection carries a close code. Both are listed in WebSockets.