Public Data API
Public Data API
The read-only endpoints under /api/v1: what the API is, the response envelope every answer shares, and CORS and caching.
The API#
| Base path | /api/v1, served from this origin |
|---|---|
| Endpoints | 27. Every one answers GET and OPTIONS, and nothing else. See endpoints. |
| Authentication | None. An X-API-Key header raises the rate limit and gates nothing. |
| Chain | One per deployment. There is no chainId parameter; the chain that answered is freshness.chainId. |
| Formats | JSON. ?format=csv on the 14 tabular endpoints. |
| CORS | Access-Control-Allow-Origin: * on every response. Credentials are never allowed. |
| Where the figures come from | The indexer’s stable views, over a read-only connection. Nothing is computed in the application. |
| The API | Rule |
|---|---|
| Serves | Only data already stored in The Hood’s own database — the indexer’s views, read over a read-only connection |
| Never calls | A 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 miss | Data 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 exposes | Our provider keys, endpoints or their error text — in a body, a header or an error |
X-API-Key | A key for this API’s rate limit. It unlocks no provider and no extra data |
The response envelope#
{
"data": { ... }, // the endpoint's own body
"freshness": { ... }, // on every response, always
"pagination": { // only on a paged endpoint
"cursor": null, // the cursor this page was read at
"next": "eyJxIjoi..." // the cursor for the next page, or null on the last
}
}data is an object rather than a bare array on every endpoint, including the list ones — a list endpoint puts its rows under a named key (launches, markets, trades, days, and so on) alongside the filters that produced them. On failure the body is the error shape instead, and no envelope is sent:
{
"error": "The Hood's public data API cannot use `limit` = \"9000\". It must be a whole number between 1 and 500; the default is 50. …",
"code": "API_V1_BAD_PARAMETER",
"details": { "parameter": "limit", "received": "9000", "min": 1, "max": 500, "fallback": 50 }
}error is the message to show a human, code is the stable string to branch on, and details carries the same subject and values as machine-readable fields. All three are sent verbatim — nothing between the failing layer and the response substitutes a generic string for a specific one, so a client that renders its own fallback over error is discarding the only useful part.
CORS, caching and conditional requests#
| Header | Value |
|---|---|
access-control-allow-origin | *. Every route is a read of public chain data, no route reads a cookie, and no route has a side effect |
access-control-allow-methods | GET, HEAD, OPTIONS |
access-control-allow-headers | content-type, x-api-key |
access-control-expose-headers | etag, the four rate-limit headers, x-thehood-as-of-block, x-thehood-coverage |
| Credentials | Never allowed. access-control-allow-credentials is not sent at all, so no future edit can make the wildcard unsafe |
| Header | Value |
|---|---|
cache-control | public, max-age=0, s-maxage=<n>, stale-while-revalidate=30. The browser always revalidates; the CDN serves one answer to everybody for <n> seconds |
etag | A strong hash of the exact bytes served. Send it back as If-None-Match and an unchanged response costs a 304 rather than a repeated database read |
content-type | application/json; charset=utf-8, or text/csv; charset=utf-8 |
# Everything an integration needs to start, in three requests.
curl -s <origin>/api/v1/health | jq '{ chainId, environment, factories, indexed, readiness }'
curl -s <origin>/api/v1/metrics | jq '.data.metrics[] | { id, unit, derivation }'
curl -s '<origin>/api/v1/launches?lifecycle=CURVE_TRADING&sort=VOLUME&limit=10' \
| jq '{ asOf: .freshness.asOfBlock, coverage: .freshness.coverage, scope: .freshness.scope,
launches: [.data.launches[] | { token, forceName, raised: .stats.netRaisedWei }] }'