Developers & API › Public Data API › Errors and limits

Public Data API

Errors and limits

Every stable error code and what to do about it; then authentication, the stored-data rule, what is public, and the rate limits a key changes.

Version
1.5
Updated
2026-10-14
Source
Public Data API library

Errors#

Every failure carries a stable code. Branch on that, not on the status: a code is part of the contract and adding one is an ordinary change, while changing one is a breaking change to whatever is already branching on it.

Error codes
CodeStatusMeaning
API_V1_BAD_PARAMETER400A path or query parameter is not the shape the endpoint accepts. details names the parameter, what arrived and what is accepted
API_V1_BAD_CURSOR400A cursor did not decode, or was minted for a different query. details carries both query fingerprints
API_V1_UNSUPPORTED_FORMAT400?format= asked for something other than json or csv
API_V1_NOT_FOUND404The subject of a single-resource route is not in the index. The message names the factories in scope and points at /api/v1/health
API_V1_DATA_NOT_STORED404The request asks for data we have not stored — today, a from/to range on /stats/daily or /fees entirely before the first stored day or after the newest indexed block’s day. Never fetched on demand. details: requestedFrom, requestedTo, storedFirstDay, storedThroughDay, asOfBlock, table. Retry with a range that overlaps the stored days
API_V1_MODERATION_UNREADABLE503The list of launches hidden from listings could not be read, so /launches refuses rather than serve an unfiltered list. Retry shortly
API_V1_RATE_LIMITED429The tier’s window is full. The exact wait is in the message and the details
API_V1_READER_SHAPE_UNEXPECTED500A query ran and what came back is not the shape the view declares — for instance a quantity that arrived as a float. Refused rather than published
API_V1_READER_UNREACHABLE503, or 500The read-only connection could not be opened or a query failed (503); an unclassified failure wrapped at the endpoint boundary answers 500, with the original message in details.underlying
API_V1_READER_URL_MISSING503The reader connection variable is not set in this environment
API_V1_READER_URL_IS_WRITER503The reader is unset while the writer is set. The API refuses to fall back onto the writer rather than working today and breaking the day a replica is introduced
API_V1_READER_DRIVER_MISSING503The Postgres driver is not installed. The message names the package and the command
API_V1_FRESHNESS_CHECKPOINT_MISSING503The indexer has written no checkpoint, so no figure can carry an asOfBlock — and no figure is published without one

A failure is sent with Cache-Control: no-store. A cached 503 outlives the fault that caused it, so an RPC that recovers in two seconds would otherwise keep serving “unreachable” for the rest of the window.

Authentication#

Credentials
QuestionAnswer
Which endpoints need credentials?None. Every endpoint under /api/v1 is public and read-only.
Which accept an optional credential?All of them, as an X-API-Key request header. It changes the rate limit and nothing else — not the data, not the fields, not which endpoints answer.
How is a key obtained?By asking. Keys are issued by hand and held in this deployment’s configuration — there is no self-service portal. A key that was not issued for the environment you are calling is not recognised there.
What happens to an unrecognised key?The request is served, and rate-limited as anonymous. The response says so: x-thehood-api-key-status: unrecognised. Nothing is rejected and no data changes.
Are cookies or bearer tokens accepted?No. CORS advertises access-control-allow-origin: * with no allow-credentials, and the only request headers accepted on a preflight are content-type, if-none-match and x-api-key.
Is any endpoint write-capable?No. Every route exports GET and OPTIONS only.

Stored data only#

Stored data only
The APIRule
ServesOnly data already stored in The Hood’s own database — the indexer’s views, read over a read-only connection
Never callsA provider on your behalf — no RPC node, price feed, explorer or social API is reached by any /api/v1 request. Chain head is never read
Never fetches on a missData we have not stored is refused with API_V1_DATA_NOT_STORED, naming what is stored. It is not fetched, back-filled or proxied
Never exposesOur provider keys, endpoints or their error text — in a body, a header or an error
X-API-KeyA key for this API’s rate limit. It unlocks no provider and no extra data

What is public, and what is not#

Rate limits and keys#

Limits
TierIdentified byRequests per minute
anonymousThe first entry of X-Forwarded-For, else X-Real-IP, else one shared bucket60
freeAn X-API-Key header carrying a key issued for this deployment. An unrecognised value is counted as anonymous, in the caller’s own IP bucket.600
pro, businessAn issued key configured onto that tier600

The counter lives in memory per function instance, so the effective limit is the figure above multiplied by however many instances are serving, and it resets when an instance is recycled. That is stated rather than hidden: a limit that is quietly ten times what it claims is a limit nobody can reason about. It still does the job it exists for, which is stopping one client opening a hundred concurrent connections against the reader from a single instance.

Headers on every response, including the 429
HeaderValue
x-ratelimit-limitThe tier’s limit
x-ratelimit-remainingRequests left in this window, floored at 0
x-ratelimit-resetUnix seconds at which the window ends
x-thehood-api-tieranonymous or the keyed tier
x-thehood-api-key-statusabsent (no header sent), recognised, or unrecognised (sent, not honoured, counted as anonymous)

A refused request is a 429 with code API_V1_RATE_LIMITED, and its details carry path, tier, limit, remaining, resetAt, resetAtIso, retryAfterSeconds — the exact wait, in the body — plus apiKeyStatus, apiKeyLabel and apiKeyFingerprint, the first 8 hex characters of the key’s SHA-256. The key itself is never echoed, in the body or anywhere else; hash your own copy to confirm which one was used.