# Response format

> The envelope every data route returns, the meta object, strict query parameters and the rules that hold on every route.

Page: https://docs.aggre.org/concepts/response-format · Section: Concepts · All pages: https://docs.aggre.org/llms.txt

## The envelope {#envelope}

Every successful data answer is JSON in the same shape:

```json
{
  "data": [ /* rows, or one object on a single-record route */ ],
  "meta": { "source": "okx", "status": "ok", "fetchedAt": "2026-01-01T00:00:00Z", "dataDate": "2025-12-31T00:00:00Z", "message": null },
  "next_cursor": null
}
```

- `data` is an array of rows (a list route) or one object (a single-record route). The shape of a row is its [JSON Schema](https://docs.aggre.org/api/schemas.md).
- `meta` says where the data came from and how fresh it is ([below](#meta)).
- `next_cursor` is `null` on the last page and on routes without paging; otherwise pass it back unchanged as `cursor` ([Pagination](https://docs.aggre.org/concepts/pagination.md)).

A few service routes differ: `GET /v1/datasets` and `GET /v1/whoami` return `{ "data": ... }` only, and `GET /healthz` / `GET /readyz` return `{ "status": "ok" | "unavailable" }`. Errors use their own envelope ([Errors](https://docs.aggre.org/concepts/errors.md)).

## The `meta` object {#meta}

| Field | Meaning |
|---|---|
| `source` | The one source of the rows, or `aggre` when they mix several. |
| `status` | `ok`, `stale` (our last read is older than the dataset's window) or `empty` (nothing matched, or nothing was ever read). Some families add other states, documented on their route. |
| `fetchedAt` | When Aggre last successfully read the source. `null` where nothing has been read yet. |
| `dataDate` | The newest data point returned. What it means per route (a bar's open time, a publisher's list date, the last read) is stated on the route. |
| `message` | A sentence the route wants you to see, or `null`. Where a dataset has a standing caveat (name lists are not a screening verdict; Congress trades are disclosed weeks late) it is here, on every response. |

Routes add keys of their own (for example `restricted`, `attribution`, `licenses` on statutes; `series` on macro observations; `dataset` on filings). **Ignore keys you do not know**: fields are added over time and never removed or renamed ([Versioning](https://docs.aggre.org/reference/versioning.md)).

## Strict query parameters {#strict}

An **unknown** or **repeated** query parameter is `400 invalid_request`, never silently ignored. So is a value out of range, a malformed date, or a cursor from another route. This catches typos (`limt=5`) before they return the wrong rows; build your requests from the parameter lists in the [reference](https://docs.aggre.org/api/data.md).

## Values and units {#values}

- Identifiers are strings. Measured values are JSON numbers. Money and on-chain amounts that need exact precision are **decimal strings**.
- Time-series timestamps are Unix milliseconds in UTC (`ts`); times inside `meta` are RFC 3339. Calendar dates are `YYYY-MM-DD`. See [Identifiers and values](https://docs.aggre.org/concepts/identifiers.md).
- Units are explicit where a value is not obvious (`unit`, `unit_multiplier`); nothing is converted on the way in, so `value × unit_multiplier` is in `unit`.
- A value a publisher leaves out is absent or `null`, never `0`.

## Headers {#headers}

Every response carries `x-request-id`; quote it when you report a problem. A `429 rate_limited` also carries `Retry-After`.
