API
Errors
Every non-2xx response is the same JSON shape with a code from one closed, append-only set. Branch on code, never on message.
The error envelope
Every error the API returns — including rejections from in front of the handler — is JSON of this shape, never HTML:
{
"error": {
"code": "not_entitled_tier",
"message": "a human-readable explanation",
"details": null
}
}codeis machine-readable and comes from the closed set below.messageis for people. Its wording can change; do not parse it.detailsis optional structured context and may benull.
Error codes
The set is closed and append-only: codes are never renamed or removed, and a new one is only added for a genuinely new failure mode. This table is generated from the API contract.
| Code | HTTP | What it means |
|---|---|---|
unauthenticated | 401 | No credential, or one the API does not recognise. Send Authorization: Bearer itm_…. |
token_expired | 401 | The credential was valid and has expired. For a key: it was revoked or rotated. |
invalid_ticket | WS | A WebSocket ticket that is malformed, expired or already used. Mint a new one per connection. |
not_entitled_tier | 403 | Your plan does not include this data, or the account has no API access. |
not_entitled_symbol | 403 | This symbol is not available to API keys (index symbols and options on them are app-only). |
attestation_required | 403 | The account has not declared a market-data classification yet. Do it once in account settings. |
rate_limited | 429 | Too many requests right now. The response carries Retry-After in seconds; wait that long, then retry. |
quota_exceeded | WS | A new stream subscription would pass your plan’s subscription or symbol cap. Unsubscribe from something first. |
invalid_params | 400 | A parameter is missing, malformed or out of range. message says which. |
not_found | 404 | No such symbol, contract, record or session. |
range_capped | — | Reserved for range limits; not returned by any public operation today. Handle it like invalid_params. |
partial_data | — | Reserved for incomplete answers; not returned today. Completeness is reported in meta on the endpoints where it varies. |
internal | 5xx | Something failed on our side. Safe reads can be retried with backoff; include the request ID if you report it. |
Retrying
- Retry
429 rate_limitedafter the number of seconds inRetry-After. - Retry safe reads (
GET) on502,503and504with exponential backoff and a cap. - Do not blindly retry writes (
POST,PUT,PATCH,DELETE).PUToperations in this API are idempotent upserts under an id you choose, so repeating one is safe; aPOSTmay not be. - Never retry
401or403without changing something first — the same request will fail the same way.
The SDKs follow exactly these rules: they retry GET on 429/502/503/504 with bounded waits (at most 30 seconds each) and never replay a write.
Reporting a problem
Include the x-request-id response header (the SDKs expose it as the result's request id and on the error). It identifies the exact request on our side.