# API

> The recorder's read-only HTTP API: base URL, conventions, status codes and examples.

A [recorder](/docs/recorder) serves what it records over plain HTTP. The API is read-only, has no authentication and allows requests from any origin. Flowscope clients load the same paths from the data hub, which runs a recorder; you can use it from `curl`, scripts or your own tools.

## Base URL

The recorder listens on `127.0.0.1:8787` by default. Change it with `--listen` or `FLOWSCOPE_RECORD_LISTEN`.

```text
http://127.0.0.1:8787
```

Every path below is relative to that base. Behind a reverse proxy the base is the proxy's URL, for example `https://data.example.com`.

## Endpoints

| Method | Path | Answer | Type |
|---|---|---|---|
| `GET` | [`/health`](/docs/api/health) | `ok` | Text |
| `GET` | [`/v1/markets`](/docs/api/markets) | Market keys recorded, one per line | Text |
| `GET` | [`/v1/segments/<key>/`](/docs/api/segments#list-days) | Days held for a market, one per line | Text |
| `GET` | [`/v1/segments/<key>/<YYYY-MM-DD>.fsd`](/docs/api/segments#get-a-day) | One day segment | Binary |
| `GET` | [`/v1/hl/coins`](/docs/api/hl-coins) | Hyperliquid coins recorded, one per line | Text |
| `GET` | [`/v1/hl/status`](/docs/api/hl-status) | Accounts, requests and coverage per coin | Text |
| `GET` | [`/v1/hl/levels/<COIN>?from=<ms>&to=<ms>`](/docs/api/hl-levels) | TP, SL and liquidation runs | Binary |
| `GET` | [`/v1/hl/footprint/<COIN>?from=<ms>&to=<ms>`](/docs/api/hl-footprint) | OI, net-long and net-short footprint minutes | Binary |
| `GET` | [`/v1/options/<COIN>[?at=<ms>]`](/docs/api/options) | A coin's Deribit option chain, the newest or the newest at `at` | Binary |

## Conventions

| Topic | Rule |
|---|---|
| Methods | `GET` and `HEAD`. Anything else answers `405`. |
| Text answers | `text/plain; charset=utf-8`, one item per line, each line ending in `\n`. An empty list is an empty body. |
| Binary answers | `application/octet-stream`, little-endian. See [Segment format](/docs/api/segment-format), [Positions codec](/docs/api/positions-codec) and [option snapshots](/docs/api/options#snapshot-format). |
| Market keys | `<Venue>_<SYMBOL>`, for example `BinanceUsdm_BTCUSDT`. Only ASCII letters, digits, `_` and `-`, shorter than 128 characters. |
| Days | `YYYY-MM-DD`, UTC. |
| Times | Milliseconds since the Unix epoch, UTC, as integers. |
| Query strings | Read only by `/v1/hl/levels`, `/v1/hl/footprint` (`from`, `to`) and `/v1/options` (`at`); ignored elsewhere. |
| Paths | Matched literally. Percent-encoding is not decoded. |
| Connections | One request per connection. Every answer carries `Connection: close`. |

## Response headers

Every answer, including errors, carries the same set of headers:

```text
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 3
Cache-Control: no-store
Access-Control-Allow-Origin: *
Connection: close
```

`Cache-Control` depends on the endpoint. Past days are cacheable forever; see [Caching and CORS](/docs/api/caching-and-cors).

## HEAD

`HEAD` answers with exactly the headers a `GET` would send, including `Content-Length`, and no body. Use it to check whether a day exists, or how large it is, without downloading it.

```bash
curl -I http://127.0.0.1:8787/v1/segments/BinanceUsdm_BTCUSDT/2026-10-03.fsd
```

```text
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 214392
Cache-Control: public, max-age=31536000, immutable
Access-Control-Allow-Origin: *
Connection: close
```

## Status codes

| Status | Reason | When | Body |
|---|---|---|---|
| `200` | OK | Success | The answer |
| `400` | Bad Request | Invalid market key, invalid day, or an invalid Hyperliquid range | `bad market key`, `bad day` or `bad range` |
| `404` | Not Found | Unknown path, a day or coin not recorded, Hyperliquid or option recording turned off, or no option snapshot yet | `not found`, `not recorded`, `coin not recorded`, `hyperliquid positions not recorded`, `options of <COIN> are not recorded`, `option chains not recorded` or `no snapshot yet` |
| `405` | Method Not Allowed | A method other than `GET` or `HEAD` | `read-only` |
| `431` | Request Header Fields Too Large | The request head is larger than 8 KiB | `request head too large` |
| `503` | Service Unavailable | A recorded Hyperliquid coin has no sample yet | `no sample yet` |

Error bodies are short plain-text lines meant for people. Use the status code in programs.

A client that does not finish sending its request head within 10 seconds is disconnected without an answer.

## CORS

Every answer carries `Access-Control-Allow-Origin: *`, so a web page on any origin can read the API with a simple `GET`. Preflight requests (`OPTIONS`) are not supported and answer `405`; plain `fetch(url)` calls do not trigger one. See [Caching and CORS](/docs/api/caching-and-cors#cors).

## Quick tour

```bash
BASE=http://127.0.0.1:8787

curl $BASE/health
curl $BASE/v1/markets
curl $BASE/v1/segments/BinanceUsdm_BTCUSDT/
curl -o 2026-10-03.fsd $BASE/v1/segments/BinanceUsdm_BTCUSDT/2026-10-03.fsd

curl $BASE/v1/hl/coins
curl $BASE/v1/hl/status

FROM=$(( ($(date +%s) - 3600) * 1000 ))
TO=$(( $(date +%s) * 1000 ))
curl -o btc.levels "$BASE/v1/hl/levels/BTC?from=$FROM&to=$TO"
curl -o btc.fp "$BASE/v1/hl/footprint/BTC?from=$FROM&to=$TO"

curl -o btc.opt $BASE/v1/options/BTC
```

## Versioning

Paths carry the API version (`/v1/`). Binary payloads carry their own format version in their header: day segments are at version 3 (versions 2 and 1 still decode), Hyperliquid payloads at version 1, option snapshots at `FSO2` (the version is in the magic; `FSO1` still decodes).
