# Volume delta (CVD) of a day

> SPX options printed as calls minus those printed as puts, the terminal's CVD band, one row a minute: what printed in the minute and the running totals since 09:30 at its end. Three scopes: today's expiry (0DTE), the near expiries the book models (_next; all expiries is the two summed), and each weighted by the option's delta at the print (cvd_delta, delta-contracts). The volume book's own sign (a call printed is a call), never a guess at which side hit which. ES answers with SPX's: these are counts of SPX options, no basis moves them. Today's rows follow the session with a minute's lag.

Source: https://firmtape.com/docs/developers/live/cvd

SPX options printed as calls minus those printed as puts, the terminal's CVD band, one row a minute: what printed in the minute and the running totals since 09:30 at its end. Three scopes: today's expiry (0DTE), the near expiries the book models (`_next`; all expiries is the two summed), and each weighted by the option's delta at the print (`cvd_delta`, delta-contracts). The volume book's own sign (a call printed is a call), never a guess at which side hit which. ES answers with SPX's: these are counts of SPX options, no basis moves them. Today's rows follow the session with a minute's lag.

`GET /api/v1/cvd`

Plan LIVE · Key scope live · Limit 120 a minute per key; the last 90 days, older with DATASET · Version 1.0.0 · Since 2026-10-27

**This endpoint serves:** [LIVE: Levels API](https://firmtape.com/docs/live#levels-api), [DATASET: Older days in the levels API](https://firmtape.com/docs/dataset#deep-history). **Plan:** [LIVE](https://firmtape.com/features/live). LIVE: today's session every second, alerts, ASK, the levels API and the platform integrations, and 5 LAB credits a month. Every finished session is free in REPLAY.

## Parameters

| Name | What it does | Default | Error |
| --- | --- | --- | --- |
| `sym` (required) query, string | SPX or ES (MES answers as ES). Other symbols answer `spx_only`. |  | missing_sym, spx_only |
| `day` query, string | `YYYY-MM-DD`, within the last 90 days; older with the DATASET licence. | the running or most recent session | bad_day, history_too_old |
| `errors` query, 200 | `200`: an error answers 200 with the same body and the real status in `X-FirmTape-Status`, for clients that cannot read a status. |  |  |
| `key` query, string | Legacy: the key in the address, for platforms that cannot send headers. Prefer the header. |  |  |

## Response

Columns and rows.

| Field | Meaning | Unit |
| --- | --- | --- |
| `sym` (string) | The symbol asked: SPX or ES. |  |
| `day` (string) | The ET trading day. |  |
| `index` ("SPX") | The options counted: SPX's, whatever was asked. |  |
| `step` ("1m") | One row a minute. |  |
| `unit` ("contracts") | `calls`, `puts`, `cvd` and their `_next` twins count contracts. |  |
| `unit_delta` ("delta_contracts") | `cvd_delta` and `cvd_delta_next` count delta-contracts: each print's size times its delta, not times 100. |  |
| `source` (recording \| rebuilt) | `recording`: the live engine's per-second counts; `rebuilt`: rebuilt from the raw tape after the day. |  |
| `as_of` (string) | The last second counted, ISO 8601 with the ET offset. |  |
| `columns` (array) | `t`, `calls`, `puts`, `cvd`, `calls_next`, `puts_next`, `cvd_next`, `cvd_delta`, `cvd_delta_next`. |  |
| `rows` (array) | One row a minute: its ET start (`HH:MM`), the contracts printed in it as calls and as puts (today's expiry), the running calls minus puts since 09:30 at its end, the same three for the near expiries, then the two running totals in delta-contracts. A series the day does not carry is null (days before 2026-10-08 carry no delta). |  |

## Errors

| Status | Code | What to do |
| --- | --- | --- |
| 400 | `missing_sym`, `bad_day`, `spx_only` | A parameter is wrong. Pass `sym`, one symbol or a comma list. |
| 401 | `no_key`, `bad_key` | No key, or an unknown or revoked one. Send `Authorization: Bearer <key>` with a key of the product's scope. |
| 403 | `live_required`, `history_too_old` | No LIVE plan, or a day older than 90 days without DATASET. Start or renew LIVE; `pricing` names the page. |
| 404 | `unknown_symbol`, `no_cvd` | No such symbol, or no volume delta for that day. Pick one from `GET /api/v1/symbols`. |
| 429 | `rate_limited` | Over a limit. Wait `Retry-After` seconds; poll with `If-None-Match`, a 304 is free. |
| 503 | `unavailable` | The volume archive did not answer. Retry shortly. |

## Caching

Answers carry an `ETag`: send it back as `If-None-Match` and an unchanged reading answers 304 with no body, not counted against the limit.

## Request

```sh
curl \
  -H "Authorization: Bearer $FIRMTAPE_LIVE_KEY" \
  "https://firmtape.com/api/v1/cvd?sym=SPX,ES"
```

```python
import os, requests

H = {"Authorization": f"Bearer {os.environ['FIRMTAPE_LIVE_KEY']}"}
r = requests.get("https://firmtape.com/api/v1/cvd?sym=SPX,ES", headers=H)
print(r.status_code, r.text[:500])
```

```js
const r = await fetch("https://firmtape.com/api/v1/cvd?sym=SPX,ES", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.FIRMTAPE_LIVE_KEY}` },
});
console.log(r.status, await r.text());
```

## Example response

```json
{
  "sym": "SPX",
  "day": "2026-10-15",
  "index": "SPX",
  "step": "1m",
  "unit": "contracts",
  "unit_delta": "delta_contracts",
  "source": "recording",
  "as_of": "2026-10-15T15:59:59-04:00",
  "columns": [
    "t",
    "calls",
    "puts",
    "cvd",
    "calls_next",
    "puts_next",
    "cvd_next",
    "cvd_delta",
    "cvd_delta_next"
  ],
  "rows": [
    [
      "09:30",
      11642,
      13445,
      -1803,
      3024,
      7441,
      -4417,
      -873.22,
      -180.72
    ],
    [
      "09:31",
      9120,
      8810,
      -1493,
      2210,
      2540,
      -4747,
      -640.5,
      -201.1
    ]
  ]
}
```

## Changes

- 2026-10-27: Added.
