FlowscopeDocs

GET /v1/options

A coin's whole Deribit option chain as recorded once a minute, with each option's recorded taker flow, the newest or the newest at a given time, as one binary snapshot.

Returns one snapshot of a coin’s option chain: every listed option with its expiry, strike, open interest, mark volatility, underlying price and recorded flow (net taker buying), as the recorder took it from Deribit. Flowscope computes gamma exposure from it.

The recorder takes a snapshot of each coin in --options-coins (BTC and ETH by default) once a minute; see Storage.

Request

Text
GET /v1/options/<COIN>[?at=<ms>]
GET /v1/options/<COIN>?from=<ms>&to=<ms>&step=<ms>
HEAD /v1/options/<COIN>[?at=<ms>]
ParameterInTypeDescription
COINPathstringBTC or ETH, as configured in --options-coins; case-sensitive
atQueryinteger, msOptional. The newest snapshot taken at or before this time. Without it, or if it does not parse, the newest snapshot.

With at, the snapshot is looked for in the UTC day of at and the day before. An at at or after the newest snapshot answers the newest.

Over time

With from, to and step (all milliseconds) the answer is the chains over [from, to): the first snapshot of every step on the UTC grid, oldest first, at most 300 of them. to - from may be a day at most and step a minute or more; otherwise 400. The body is a series of records, each length u32 (little-endian) followed by that many bytes of one snapshot in the format below. A range with nothing recorded answers 200 with an empty body. Flowscope’s Gamma Exposure study draws its exposure over time from these.

Response

HeaderValue
Content-Typeapplication/octet-stream
Cache-Controlno-cache

The body is one snapshot in the format below. Its time (ts) says when it was taken; the recorder keeps answering the last one it has if Deribit does not answer, so check ts if you need a fresh chain.

A BTC snapshot of about 950 options is about 10 to 12 kB.

Snapshot format

Little-endian, no padding. A 16-byte header, a raw DEFLATE stream holding the chain, and a checksum:

Text
"FSO2" | ts i64 | raw length u32 | deflate(index f64 | n u32 |
  n × (expiry i64, strike f64, put u8, open interest f32, mark iv f32, underlying f64,
  flow f32)) | fnv1a-32 u32

The reference implementation is flowscope_engine::options (encode, decode).

OffsetSizeTypeFieldMeaning
04bytesmagicASCII FSO2; the format’s version is part of it. Snapshots recorded before October 2026 are FSO1, see Version 1
48i64tsWhen the snapshot was taken, ms since the epoch
124u32raw lengthLength of the chain after decompression: 12 + 37 × n
16total − 20byteschainThe chain, raw DEFLATE (RFC 1951, no zlib or gzip wrapper)

Chain

What the DEFLATE stream decompresses to:

OffsetSizeTypeFieldMeaning
08f64indexThe coin’s index price when the snapshot was taken (Deribit’s estimated delivery price); 0 if Deribit gave none
84u32nNumber of options
1237 × noptionsOne record per option, below

Option record

37 bytes each, in the order Deribit listed them.

OffsetSizeTypeFieldMeaning
08i64expiryExpiry, ms since the epoch: 08:00 UTC of the day in the option’s name
88f64strikeStrike price, USD
161u8put0 for a call, 1 for a put; readers treat any non-zero value as a put
174f32open interestOpen interest in the coin (one Deribit BTC or ETH option is one coin)
214f32mark ivMark implied volatility, percent a year (41.25 is 41.25 %)
258f64underlyingPrice of what the option settles on: the future of its expiry
334f32flowNet taker buying recorded, in the coin: the amount of every trade of this option the recorder saw where the taker bought, minus every one where the taker sold, from when recording began. 0 where no trade was seen

Options without a mark volatility or an underlying price in Deribit’s answer are left out. Options with no open interest are kept, with 0.

The recorder follows Deribit’s option trades (get_last_trades_by_currency, kind=option) once a minute, just before it takes the chain, and adds each trade’s amount to its option’s flow with the sign of the taker’s direction. Block trades count too. The flow carries on across snapshots and is dropped when the option expires. After a restart the recorder goes on from the flow in the newest snapshot on disk (from the UTC day of the restart or the day before) and asks for the trades since then; without one it starts at 0. Trades between the last trade request and a crash can be missed.

Trailer

SizeTypeField
4u32FNV-1a 32 of every byte of the snapshot before the trailer, from the magic on (the compressed chain, not the decompressed one)

Reading rules

A reader rejects a snapshot that:

  • is shorter than 20 bytes or does not start with FSO2 or FSO1;
  • fails the checksum;
  • has a raw length below 12 or above 16 MiB;
  • does not decompress, or decompresses to anything but 12 + 37 × n bytes (12 + 33 × n for FSO1).

Version 1

FSO1 snapshots, recorded before the flow was, are the same with 33-byte option records: everything up to underlying, no flow. Readers take their flow as 0. Day files may hold both versions; each record carries its own magic.

The name of each option on Deribit is <COIN>-<DMMMYY>-<STRIKE>-<C|P>, for example BTC-27NOV26-73000-P: a put at 73 000 expiring on 27 November 2026 at 08:00 UTC. The record carries the parts, not the name.

Status codes

StatusBodyWhen
200The snapshotA snapshot was found
404options of <COIN> are not recordedThe coin is not in --options-coins, or has no options (only BTC and ETH do)
404no snapshot yetThe coin is recorded but has no snapshot: none taken since the recorder started (the first comes within a minute), or none at or before at
404option chains not recorded--options-coins is empty. Sent with Cache-Control: no-store.
404not foundThe path names no coin (/v1/options/) or has more after it
400from < to, a day at most, step a minute or moreAn over time request outside those bounds

Example

The newest BTC chain, and the one at a given time:

Terminal
curl -fsS -o btc.opt http://127.0.0.1:8787/v1/options/BTC
curl -fsS -o btc-then.opt "http://127.0.0.1:8787/v1/options/BTC?at=1791000000000"
xxd btc.opt | head -1
Text
00000000: 4653 4f32 0036 ebff a001 0000 e887 0000  FSO2.6..........

The magic FSO2, the time (0x01a0ffeb3600 = 1 791 000 000 000 ms), then the raw length (0x87e8 = 34 792 bytes: 12 + 37 × 940, so 940 options). The values after the magic are illustrative; the compressed chain follows from offset 16.

Decoding in Rust

Rust
use flowscope_engine::options::{Expiries, Positioning, decode, gex};

let bytes = std::fs::read("btc.opt")?;
let s = decode(&bytes)?;
println!("{} options at {}, index {}", s.quotes.len(), s.ts, s.index);
let g = gex(&s, s.ts, Expiries::All, Positioning::Flow);
println!("call wall {:?} put wall {:?} flip {:?} net ${:.0} per 1 %", g.call_wall, g.put_wall, g.flip, g.total);

Decoding in Python

python
import struct, zlib

b = open("btc.opt", "rb").read()
assert b[:4] in (b"FSO2", b"FSO1")
body, (fnv,) = b[:-4], struct.unpack("<I", b[-4:])
h = 0x811C9DC5
for x in body:
    h = ((h ^ x) * 0x01000193) & 0xFFFFFFFF
assert h == fnv, "checksum"
ts, raw_len = struct.unpack_from("<qI", b, 4)
raw = zlib.decompress(body[16:], -15)  # raw DEFLATE
assert len(raw) == raw_len
index, n = struct.unpack_from("<dI", raw, 0)
row = 37 if b[:4] == b"FSO2" else 33
for i in range(n):
    expiry, strike, put, oi, iv, und = struct.unpack_from("<qdBffd", raw, 12 + row * i)
    flow = struct.unpack_from("<f", raw, 12 + row * i + 33)[0] if row == 37 else 0.0

How Flowscope clients use it

A client asks its data hub for /v1/options/<COIN> once a minute while a chart shows the Gamma Exposure study, for the coin of the chart’s market. After a failed request it waits a minute longer for each failure in a row, ten minutes at most. It does not use at.