# STREAM: the session, every second

> The whole terminal stream for a licensee's server: every second of the SPX session, the levels of the three books and the book by strike, as Server-Sent Events. Real time on STREAM and STREAM 250, fifteen minutes late on STREAM, 15 MIN. One licence key opens it and the levels API.

Source: https://firmtape.com/docs/developers/dataset/stream

The whole terminal stream for a licensee's server: every second of the SPX session, the levels of the three books and the book by strike, as Server-Sent Events. Real time on STREAM and STREAM 250, fifteen minutes late on STREAM, 15 MIN. One licence key opens it and the levels API.

`https://stream.firmtape.com/v1/stream`

**This page serves:** [DATASET: Licensing for your product](https://firmtape.com/docs/dataset#licensing). **Plan:** [Redistribution licences](https://firmtape.com/features/dataset). Show FirmTape's levels or its stream to other people in your app, site, bot or channel, on printed terms

`GET https://stream.firmtape.com/v1/stream` with `Authorization: Bearer lic_...`, the licence key issued with the countersigned licence. The answer is `text/event-stream` and never ends: one `second` event per second of the session (09:30:00 to 15:59:59 ET), a comment line (`: ping`) every 15 seconds between and outside sessions. Ask for gzip (`Accept-Encoding: gzip`); every common HTTP client does.

**Your server connects, not your people's browsers.** The key is a server's credential: it holds as many connections at once as the licence says (two unless agreed), and your server shows the numbers to the people the licence covers.

| Event line | What it is |
| --- | --- |
| `id:` | the second, ET with its offset (`2026-10-23T15:59:59-04:00`). Keep the last one you took. |
| `event: second` | always this name, on both the real-time and the delayed stream. |
| `data:` | one JSON object on one line, below. |
| `retry:` | once, on connect: how long to wait before reconnecting, in ms (1 to 5 s, spread on purpose). |

**Reconnecting loses nothing.** Send the last `id` you took as `Last-Event-ID` (an EventSource client does it for you) and the stream first sends every second after it, from the last thirty minutes, then the live ones, each once and in order. Without it you start at the newest second. A deploy on our side closes the connection; reconnect and resume.

| Answer | Means |
| --- | --- |
| `200` | the stream |
| `401` | no key, a key we do not know, a revoked licence or one past its last day |
| `400` | a `Last-Event-ID` that is not a second's id |
| `429` | the licence's connections are all open; close one (a connection you dropped is freed within seconds) |

**The second.** Everything is FirmTape's own measurement of SPX options; units and books are the [data dictionary](https://firmtape.com/dataset/dictionary)'s. `null` means not measured at that second, never zero. A field is added to v1, never renamed or removed.

| Field | Book | What it is |
| --- | --- | --- |
| `v` |  | 1, the format |
| `t`, `day` |  | the second (the event's id) and its ET session date |
| `spot` | none | SPX solved from put-call parity on the chain, that second |
| `volume.flip`, `.call_wall`, `.put_wall`, `.net_gamma` | volume | the levels the terminal draws by default: the zero-gamma flip, the peak positive and negative gamma strikes within 2.5% of spot, and the net gamma at spot ($M per index point), 0DTE |
| `open_interest.flip`, `.call_wall`, `.put_wall`, `.net_gamma` | open interest | the same on the morning open interest; `call_wall_all` and `put_wall_all` over the near expiries |
| `measured.flip`, `.flip_se`, `.call_wall`, `.put_wall`, `.net_gamma`, `.charm`, `.vanna` | measured | the dealer book signed print by print: its flip (smoothed, minute cadence) and the flip's standard error, walls, net gamma, charm ($Bn of delta per day) and vanna ($M of delta per vol point) |
| `measured.max_change.<1\|5\|10\|15\|30>` | measured | `{ strike, change }`: the strike whose gamma moved most over that many minutes, and by how much |
| `prints.contracts`, `.calls`, `.puts`, `.calls_near`, `.puts_near` | volume | contracts printed in that second: 0DTE in all, calls, puts; `_near` the next three expiries |
| `prints.call_delta`, `.put_delta`, `.call_delta_near`, `.put_delta_near` | volume | the same prints weighted by each option's absolute delta |
| `ladder.minute`, `ladder.strikes` |  | the book by strike at that second: its minute and the strikes within 2.5% of spot. `ladder` is `null` on a second sent again from the backlog after a gap of yours |
| `ladder.volume.*`, `ladder.open_interest.*`, `ladder.measured.*` | per book | per strike, aligned to `strikes`: `gamma`, `vanna`, `charm` in every book; `call_contracts`, `put_contracts` (volume and open interest); `gross_gamma` (open interest, unsigned); `delta`, `vega` (measured). Each book's `zero_dte` holds the same for today's expiry alone |
| `ladder.iv.calls`, `ladder.iv.puts` | none | the 0DTE smile per strike, vol points, calendar time |

```json
{
 "v": 1,
 "t": "2026-10-23T15:59:59-04:00",
 "day": "2026-10-23",
 "spot": 7623.32,
 "volume": {
  "flip": 7631.54,
  "call_wall": 7610,
  "put_wall": 7625,
  "net_gamma": -14119.87
 },
 "open_interest": {
  "flip": 7695.82,
  "call_wall": 7605,
  "put_wall": 7625,
  "call_wall_all": 7605,
  "put_wall_all": 7625,
  "net_gamma": -731.72
 },
 "measured": {
  "flip": 7705.77,
  "flip_se": 1.69,
  "call_wall": 7625,
  "put_wall": 7630,
  "net_gamma": 8.88,
  "charm": -147.25,
  "vanna": -95.72,
  "max_change": {
   "1": {
    "strike": 7625,
    "change": 21.64
   },
   "5": {
    "strike": 7625,
    "change": 63.84
   },
   "10": null,
   "15": null,
   "30": null
  }
 },
 "prints": {
  "contracts": 86,
  "calls": 43,
  "puts": 27,
  "calls_near": 4,
  "puts_near": 12,
  "call_delta": 9.6,
  "put_delta": 19.53,
  "call_delta_near": 0.34,
  "put_delta_near": 5.23
 },
 "ladder": {
  "minute": "15:59",
  "strikes": [
   7620,
   7625
  ],
  "volume": {
   "gamma": [
    1.2,
    -2.4
   ],
   "vanna": [
    0.1,
    -0.3
   ],
   "charm": [
    0.01,
    -0.02
   ],
   "call_contracts": [
    1200,
    800
   ],
   "put_contracts": [
    900,
    2100
   ],
   "zero_dte": {
    "gamma": [
     0.9,
     -2.1
    ],
    "vanna": [
     0.1,
     -0.2
    ],
    "charm": [
     0.01,
     -0.02
    ],
    "call_contracts": [
     1100,
     700
    ],
    "put_contracts": [
     850,
     2000
    ]
   }
  },
  "open_interest": {
   "gamma": [
    3.1,
    -1.2
   ],
   "vanna": [
    0.4,
    -0.1
   ],
   "charm": [
    0.03,
    -0.01
   ],
   "gross_gamma": [
    5.2,
    4.8
   ],
   "call_contracts": [
    4100,
    3900
   ],
   "put_contracts": [
    5200,
    6100
   ],
   "zero_dte": {
    "gamma": [
     1.1,
     -0.9
    ],
    "vanna": [
     0.1,
     -0.1
    ],
    "charm": [
     0.01,
     -0.01
    ],
    "gross_gamma": [
     2.0,
     1.9
    ],
    "call_contracts": [
     1500,
     1300
    ],
    "put_contracts": [
     1800,
     2200
    ]
   }
  },
  "measured": {
   "gamma": [
    0.4,
    -0.6
   ],
   "vanna": [
    0.02,
    -0.03
   ],
   "charm": [
    0.001,
    -0.002
   ],
   "delta": [
    12.5,
    -8.1
   ],
   "vega": [
    0.21,
    -0.18
   ],
   "zero_dte": {
    "gamma": [
     0.3,
     -0.5
    ],
    "vanna": [
     0.02,
     -0.02
    ],
    "charm": [
     0.001,
     -0.001
    ],
    "delta": [
     10.2,
     -7.4
    ],
    "vega": [
     0.15,
     -0.12
    ]
   }
  },
  "iv": {
   "calls": [
    12.5,
    11.9
   ],
   "puts": [
    13.1,
    12.8
   ]
  }
 }
}
```

**Not in it, by design:** the vendor's raw quotes and trades, and the tickers' reference prices. The stream is SPX; the tickers' levels are on the levels API with the same key.

```python
import json, requests

KEY = "lic_..."
last = None
while True:
    h = {"Authorization": f"Bearer {KEY}", "Accept": "text/event-stream"}
    if last:
        h["Last-Event-ID"] = last
    try:
        with requests.get("https://stream.firmtape.com/v1/stream", headers=h, stream=True, timeout=(10, 60)) as r:
            r.raise_for_status()
            for line in r.iter_lines(decode_unicode=True):
                if line.startswith("id: "):
                    last = line[4:]
                elif line.startswith("data: "):
                    s = json.loads(line[6:])
                    print(s["t"], s["spot"], s["volume"]["flip"])
    except requests.RequestException as e:
        print("reconnecting:", e)
```
