Developers & API › Public Data API › Overview and envelope

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.

Version
1.6
Updated
2026-10-14
Source
Public Data API routes · Public Data API library · Metric definitions v1.2

The API#

At a glance
Base path/api/v1, served from this origin
Endpoints27. Every one answers GET and OPTIONS, and nothing else. See endpoints.
AuthenticationNone. An X-API-Key header raises the rate limit and gates nothing.
ChainOne per deployment. There is no chainId parameter; the chain that answered is freshness.chainId.
FormatsJSON. ?format=csv on the 14 tabular endpoints.
CORSAccess-Control-Allow-Origin: * on every response. Credentials are never allowed.
Where the figures come fromThe indexer’s stable views, over a read-only connection. Nothing is computed in the application.
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

The response envelope#

code
{
  "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:

code
{
  "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#

CORS
HeaderValue
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-methodsGET, HEAD, OPTIONS
access-control-allow-headerscontent-type, x-api-key
access-control-expose-headersetag, the four rate-limit headers, x-thehood-as-of-block, x-thehood-coverage
CredentialsNever allowed. access-control-allow-credentials is not sent at all, so no future edit can make the wildcard unsafe
Caching and conditional requests
HeaderValue
cache-controlpublic, max-age=0, s-maxage=<n>, stale-while-revalidate=30. The browser always revalidates; the CDN serves one answer to everybody for <n> seconds
etagA 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-typeapplication/json; charset=utf-8, or text/csv; charset=utf-8
code
# 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 }] }'