> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockworks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Responses

> Every Blockworks API response uses the same error/data envelope. Learn the tabular row shape and the timeseries point_schema/series shape.

Every endpoint returns the same JSON envelope, so a single response handler works across the whole
API.

```json theme={null}
{
  "error": null,
  "data": {}
}
```

| Field      | Type             | Description                                                                                                                                                                          |
| ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `error`    | string or `null` | `null` on success. On failure, a human-readable message describing what went wrong. Check this first.                                                                                |
| `data`     | varies           | The payload. Its shape depends on the endpoint: an array of rows, a single row, or a timeseries object.                                                                              |
| `metadata` | object           | Only on the two tabular data endpoints: pagination totals on the list endpoint, the matched field on the row endpoint. Timeseries responses and the catalogs have no `metadata` key. |

<Note>
  Always branch on `error` before reading `data`. On an error response, `data` is `null`.
</Note>

The one exception is the [Charts endpoints](/api-reference/charts/list), which return a
`{ "page": ..., "total": ..., "data": ... }` page object and report errors with `statusCode`
and `message`.

## Tabular responses

### List endpoints

`GET /query/tabular/{model}` returns `data` as an **array of row objects**. Each key is the model's
public field name; the set of fields comes from the catalog (and from `selections`, if you passed
it). `metadata` carries the totals for the whole filtered result set, not just the current page.

```json theme={null}
// GET /query/tabular/assets?slugIsOneOf=bitcoin,ethereum&selections=assetID,slug,prioritySymbol,name
{
  "error": null,
  "data": [
    {
      "assetID": "1e31218a-e44e-4285-820c-8282ee222035",
      "slug": "bitcoin",
      "prioritySymbol": "BTC",
      "name": "Bitcoin"
    },
    {
      "assetID": "21c795f5-1bfd-40c3-858e-e9d7e820c6d0",
      "slug": "ethereum",
      "prioritySymbol": "ETH",
      "name": "Ethereum"
    }
  ],
  "metadata": {
    "totalRows": 2,
    "totalPages": 1
  }
}
```

| Metadata field | Type    | Description                                  |
| -------------- | ------- | -------------------------------------------- |
| `totalRows`    | integer | Rows matching the filters, across all pages. |
| `totalPages`   | integer | Pages at the current `pageSize`.             |

### Single-row endpoints

`GET /query/tabular/{model}/{id}` returns `data` as a **single row object**. Its `metadata` reports
which public field the lookup value matched on. Many models accept alternative identifiers (a slug
or a symbol) in addition to the primary key.

```json theme={null}
// GET /query/tabular/assets/bitcoin?selections=assetID,slug,prioritySymbol,name
{
  "error": null,
  "data": {
    "assetID": "1e31218a-e44e-4285-820c-8282ee222035",
    "slug": "bitcoin",
    "prioritySymbol": "BTC",
    "name": "Bitcoin"
  },
  "metadata": {
    "matchedField": "slug"
  }
}
```

## Timeseries responses

Timeseries responses are column-oriented: a `point_schema` describes the values, and each point is a
compact array rather than a repeated object. This keeps large windows small on the wire.

### Multi-series endpoints

`GET /query/timeseries/{model}/{granularity}` returns `data` with `point_schema` and a `series`
array.

```json theme={null}
// GET /query/timeseries/asset-price/1d?series=1e31218a-e44e-4285-820c-8282ee222035,21c795f5-1bfd-40c3-858e-e9d7e820c6d0&timeframe=7d&selections=close,volume (trimmed to two points per series)
{
  "error": null,
  "data": {
    "point_schema": [
      {
        "name": "Time",
        "field": "time",
        "description": "Timestamp of the data point.",
        "type": "time",
        "time": true
      },
      {
        "name": "Close Price",
        "field": "close",
        "description": "Price at the candle close.",
        "type": "float64",
        "unit": "usd",
        "time": false
      },
      {
        "name": "Volume",
        "field": "volume",
        "description": "Total trade volume during the candle.",
        "type": "float64",
        "time": false
      }
    ],
    "series": [
      {
        "key": "1e31218a-e44e-4285-820c-8282ee222035",
        "entity": {
          "name": "Bitcoin",
          "slug": "bitcoin",
          "symbol": "BTC"
        },
        "points": [
          [
            1788998400,
            76547.51051673751,
            15639703479.254776
          ],
          [
            1789084800,
            77193.79460653824,
            18539958201.35965
          ]
        ]
      },
      {
        "key": "21c795f5-1bfd-40c3-858e-e9d7e820c6d0",
        "entity": {
          "name": "Ethereum",
          "slug": "ethereum",
          "symbol": "ETH"
        },
        "points": [
          [
            1788998400,
            2436.9544850136367,
            8606487184.975586
          ],
          [
            1789084800,
            2515.2085560418855,
            13665738383.303394
          ]
        ]
      }
    ]
  }
}
```

### Single-series endpoints

`GET /query/timeseries/{model}/{granularity}/{seriesKey}` flattens the same structure: `key`,
`entity`, `point_schema`, and `points` sit directly on `data`, with no `series` array and no
`metadata`.

```json theme={null}
// GET /query/timeseries/asset-price/1d/1e31218a-e44e-4285-820c-8282ee222035?timeframe=7d&selections=close,volume (trimmed to two points)
{
  "error": null,
  "data": {
    "point_schema": [
      {
        "name": "Time",
        "field": "time",
        "description": "Timestamp of the data point.",
        "type": "time",
        "time": true
      },
      {
        "name": "Close Price",
        "field": "close",
        "description": "Price at the candle close.",
        "type": "float64",
        "unit": "usd",
        "time": false
      },
      {
        "name": "Volume",
        "field": "volume",
        "description": "Total trade volume during the candle.",
        "type": "float64",
        "time": false
      }
    ],
    "key": "1e31218a-e44e-4285-820c-8282ee222035",
    "entity": {
      "name": "Bitcoin",
      "slug": "bitcoin",
      "symbol": "BTC"
    },
    "points": [
      [
        1788998400,
        76547.51051673751,
        15639703479.254776
      ],
      [
        1789084800,
        77193.79460653824,
        18539958201.35965
      ]
    ]
  }
}
```

### Reading points

<Warning>
  A point is an array, not an object. The first element is always a **unix timestamp in seconds**;
  the values that follow are the metrics in `point_schema` order. Never assume a fixed column
  order. Read `point_schema` and index by it.
</Warning>

Individual values may be `null` where the underlying data has a gap.

```javascript theme={null}
const { point_schema, series } = data;
// Skip the leading timestamp entry when mapping metric fields to point indexes.
const fields = point_schema.map((column) => column.field);

for (const { key, points } of series) {
  for (const point of points) {
    const row = { time: new Date(point[0] * 1000) };
    fields.slice(1).forEach((field, i) => {
      row[field] = point[i + 1];
    });
    console.log(key, row);
  }
}
```

### point\_schema fields

| Field         | Description                                                                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`        | Human-readable column name.                                                                                                                       |
| `field`       | Machine-readable field name; matches the values accepted by `selections`.                                                                         |
| `description` | What the metric measures.                                                                                                                         |
| `type`        | Data type, e.g. `time`, `float64`, `int64`.                                                                                                       |
| `time`        | `true` for the leading timestamp column.                                                                                                          |
| `unit`        | What the value is denominated in: `usd`, `btc`, `sats`, `eth`, `gwei`, `native-token`, `percent`, `count`, or `ratio`. Absent for a plain number. |

The `entity` object describes what the series key stands for. For asset-keyed models, that is the
asset's `name`, `slug`, and `symbol`. Any of these may be `null`.

<Warning>
  A series key that matches nothing is not an error. `GET /query/timeseries/blockchains/1d/solana`
  returns `200` with `"points": []` and no `entity`, because the key must be the network's
  `networkID`, not its slug. See [Finding an id](/api-reference/data-api/discovery/finding-an-id).
</Warning>

## Alternative formats

Every data endpoint can return CSV or newline-delimited JSON instead of the envelope. See
[Filtering & pagination](/getting-started/filtering-pagination#response-formats).

## Response headers

| Header          | Description                                                                                                                                |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `Cache-Control` | Derived from the model's freshness, with `stale-while-revalidate`. Gated models are marked `private` so they are never cached at the edge. |
| `Cache-Status`  | `query-engine-cache; hit` or `query-engine-cache; miss`, reporting whether the query cache served the response.                            |
| `X-Request-Id`  | Unique identifier for the request. Include it when contacting support about a specific call.                                               |
