Errors
Every error the API returns has one envelope and a code from a closed set. The reasons inside a field_states entry
are a different list (field states).
The error envelope
Every error uses OpenAI's envelope, with one field added, retry:
{"error": {"type": "invalid_request_error", "code": "not_maintained",
"message": "fx:USDEUR is not maintained; the maintained pair is fx:EURUSD (USDEUR = 1 / EURUSD)",
"param": "pair", "retry": "never"}}
| field | meaning |
|---|---|
type | invalid_request_error for a 4xx, server_error for a 5xx |
code | the cause, from the closed set below; branch on it, never on message |
message | a human-readable explanation; its wording can change |
param | the parameter at fault, when there is one; otherwise null |
retry | later: the same request may succeed later; never: the answer is settled, do not retry; change_request: a different request can succeed, and the message names it |
The tables below give retry per code. An error worth retrying soon also carries Retry-After (seconds, the earliest
useful retry) and x-should-retry: true; every other error carries x-should-retry: false. A
503 temporarily_unavailable sends Retry-After: 300 unless its cause is known to last longer, when it sends that
longer wait. A 503 service_unavailable or identity_provider_unavailable means a Tauric service is briefly down: it
sends Retry-After: 10 or less. 429 quota_exhausted has no Retry-After; retry at its resets_at.
A few errors carry extra fields, named in their rows. A 409 ambiguous_instrument for a crypto asset spelling
carries candidates (crypto).
The model-inference and copilot routes use the same envelope with their own OpenAI-style codes, listed on model inference and copilot.
Error codes
A data code describes the data, so an immediate retry returns the same answer.
Data and coverage
| code | HTTP status | meaning | retry |
|---|---|---|---|
not_registered | 404 | a well-formed spelling that is not registered; for an external or retired spelling, the message names the key to use; otherwise search GET /v1/instruments?q=… (find an instrument) | change_request |
not_maintained | 404 | registered, but this data is not maintained for it; a listing that left coverage carries maintenance (state: departed, coverage_end), and its history stays readable (listing lifecycle) | never |
delisted | 404 | a current read (quote, batch quote, technicals, analyst consensus, earnings calendar, research) of a delisted listing; its history is served through its last session, and the message names the effective date, the reason, and the SEC forms. Also returned when another listing now holds the symbol (listing lifecycle) | never |
not_supported | 404 | outside what the API serves, for example a security with no SEC filings served, or a route of the wrong shape for a series; the message names the right one | never |
not_published | 404 | nothing is published for it (for example, a crypto pair with no trade in 24 hours) | never |
temporarily_unavailable | 503 | no current value can be served right now; the message says why and Retry-After when to retry | later |
insufficient_history | 404 | the stored history does not reach as far back as asked | change_request |
ambiguous_instrument | 409 | a spelling names more than one instrument; the message names them | change_request |
The request
| code | HTTP status | meaning | retry |
|---|---|---|---|
invalid_request | 400 / 422 | the request is malformed: a bad value, an unknown or repeated query parameter, an unknown body field; param names it | change_request |
not_found | 404 | no such path, id, or document | change_request |
no_report | 404 | the research run is yours and has no stored report: it did not complete, or you deleted the report | change_request |
method_not_allowed | 405 | the path exists, but not with this method | change_request |
request_too_large | 413 | the request body is too large | change_request |
conflict | 409 | the request conflicts with the current state of the resource | change_request |
gone | 410 | the resource existed and is no longer available | never |
unsupported_media_type | 415 | the body's Content-Type or Content-Encoding is not accepted | change_request |
range_not_satisfiable | 416 | the Range is not one range starting below the size | change_request |
session_deleted | 410 | the session was deleted; its id stays reserved | never |
model_not_found | 400 / 404 | a model other than the served one; the message names the served id | change_request |
Keys, access, and accounts
| code | HTTP status | meaning | retry |
|---|---|---|---|
missing_api_key | 401 | no API key was sent | change_request |
invalid_api_key | 401 | the key is unknown or malformed | change_request |
api_key_inactive | 401 | the key was revoked | change_request |
api_key_expired | 403 | the key has expired | change_request |
account_inactive | 401 | the account the key belongs to is suspended | never |
access_not_granted | 403 | the account is not active, or its tier includes no website access (no data, ai, or sessions entitlement) | change_request |
entitlement_required | 403 | your tier, or this kind of credential, does not include what this operation needs; the error's entitlement names it: data, ai, mcp, or sessions (entitlements) | change_request |
forbidden | 403 | the key is not allowed this operation | change_request |
principal_not_permitted | 403 | this credential cannot call this operation; use your own key | change_request |
owner_session_required | 403 | managing keys and sessions (/v1/account/…) needs the owner's website session, not an API key | change_request |
credential_limit_reached | 409 | the account already holds its maximum of live keys; revoke one first | change_request |
invalid_session | 401 | a website session is missing or invalid | change_request |
invalid_exchange_token, invalid_identity_token, csrf_failed, connection_unknown, connection_revoked, connection_replayed, invalid_grant | 401 / 403 | The sign-in or app connection is no longer valid: sign in again, or reconnect the app. | change_request |
identity_provider_unavailable | 503 | website sign-in is briefly unavailable; retry after Retry-After seconds (API keys are unaffected) | later |
Limits and the server
| code | HTTP status | meaning | retry |
|---|---|---|---|
concurrency_limit_reached | 429 | the account already has its limit of research runs, copilot runs, and model-inference requests in progress at once (limit and in_flight are given); retry after Retry-After seconds, when one finishes | later |
rate_limit_exceeded | 429 | the per-minute rate limit is exceeded; wait for the reset header (rate limit) | later |
quota_exhausted | 429 | a daily credit pool is spent: pool names it (data or ai), with remaining credits and resets_at, the next midnight UTC; the other pool may still have credits | later |
server_error | 500 | a fault on Tauric's side; report it (report an issue) | later |
service_unavailable | 503 | temporarily unavailable; retry after Retry-After | later |
The MCP server
The hosted MCP server passes the API's typed errors through with the same code, and adds these
(use with AI assistants):
| code | returned as | meaning | retry |
|---|---|---|---|
missing_required_argument | tool error result | a required argument is missing; param and missing name it, and no request was made | change_request |
unknown_parameter | tool error result | an argument the tool does not declare (a typo, or known_at on a tool that takes none); param and unknown name it, and no request was made. REST refuses the same mistake as 422 invalid_request | change_request |
response_too_large | tool error result | the result is over the size limit; nothing is returned, never a truncated result. The request was made, so it is charged (response size limit) | change_request |
invalid_authorization | 401 | the Authorization header is not exactly one Bearer <key> | change_request |
invalid_token | 401 | the access token from an MCP sign-in is not valid or has expired; the app refreshes it, or you connect the app again | change_request |
Rules for every request
Every /v1 route answers 422 invalid_request, with param naming the parameter, for an undeclared query
parameter, a parameter sent twice, or a value outside a parameter's schema. An unknown field in a POST body is 422 invalid_request on the data routes and 400 unsupported_parameter on the copilot and model-inference routes. A batch request
answers each key with the code its own route would answer
(batch quotes). An unknown path answers 404 not_found; the
API reference lists every route.
Report an issue
Send one report per issue through the contact form at https://tauric.ai/contact, with:
- Request: the method, full path and query, and body (never the key, a session cookie, or an
Authorizationheader). - Response: the status, the full body (or the
errorobject), and thex-ratelimit-*andx-quota-*headers. - Time: the UTC time of the call.
- Expected and actual: why it is wrong, such as a wrong number, an expected data field that is null with no
field_statesentry, a code that contradicts/v1/datasets, a500, or two routes that disagree (give both). - Repeatability: whether it happened once or always, and whether a retry changed it.
A typed not_supported or not_maintained error, or a not_held reason, is expected behavior unless /v1/datasets and
GET /v1/instruments/{key} say the dataset is served for that instrument.