# Choose your dataset: observations, candles and trades

API v2 is the host `https://apiv2.laevitas.ch`; `/api/v1` is its URL namespace,
not a request to use the old API. V2 serves both modern products and retained
history migrated from v1. Those products do **not** all have the same start date,
timestamp convention, fields or aggregation rule.

For an existing collector, start with the [exact migration mapping](legacy-history-routes.json).
For new research, choose the product below before choosing a resolution.
The definitions are also available at `GET /api/v1/changelog/data-definitions`
and MCP `get_data_definitions`. Pass `id`, for example `options.snapshot`, to
select one definition. This catalogue covers the options, spot, perpetual and
dated-futures instrument products and their retained-history counterparts, plus
the analytics families listed below. It is not the full API route inventory.
Positioning, market summaries and other unlisted analytics remain outside this catalog. The
[retained-history field guide](history-field-guide.md) covers the other migrated
families, including liquidations and derived analytics.

For the retired exchange, see [BitMEX historical data](bitmex-history.md):
preserved date ranges, important coverage limits and unchanged historical access.

## Derived analytics: choose the calculation, not just the name

The [analytics definitions reference](analytics-definitions.md) is generated from
the same definitions as Swagger and MCP. It includes formulas, units, time
selection, coverage limitations and transport differences. No values, response
field names or historical calculations changed when these definitions were added.

| Family | Definition IDs | Important distinction |
| --- | --- | --- |
| Options trade aggregates | `options.flow`, `options.trades_summary` | Trade Greeks already include size and buy/sell sign. They are not unit Greeks or outstanding dealer inventory. |
| Dealer GEX | `options.gex_catalog`, `options.gex_latest`, `options.gex_history`, `options.gex_regime_changes`, `options.gex_strikes`, `options.gex_term_structure`, `options.gex_profile` | Weighted and raw contributions differ. Older methodology boundaries prevent treating the whole history as one comparable model. |
| Quote-selected surfaces | `options.vol_surface_by_expiry`, `options.vol_surface_by_tenor`, `options.vol_surface_by_time` | Nearest-delta exchange mark IV; coarse intervals can select quotes from different times. Not fitted smiles. |
| Fitted surfaces | `vol_surface.catalog`, `vol_surface.snapshots`, `vol_surface.slices`, `vol_surface.term_structure`, `vol_surface.strikes` | Separate margin books and models; inspect quality and fallback fields. A calibration timestamp is not original availability. |
| Portfolio risk | `vol_surface.risk_decompose`, `vol_surface.risk_scenario`, `vol_surface.risk_ladder` | Current on-demand calculations, not historical replay. PnL fractions and percent-valued shock inputs use different scales. |
| Instrument realized volatility | `analytics.realized_volatility` | Perpetual mark-price estimators with hourly/daily inputs, not aggregate-spot RV. Inputs can include an unfinished interval. |

For reproducible comparisons, provide explicit bounds and selectors. Some
omitted-bound defaults differ between REST and MCP; the reference states these
differences rather than implying that all products share one default.

## Which options product should I use?

All paths below are relative to `/api/v1`. These are different views, not names
for interchangeable data.

| Product and definition ID | Route | What one row means |
| --- | --- | --- |
| Retained instrument observations — `options.instrument_history` | `/options/instrument-history` | Legacy price, IV, Greeks, references, OI and volume state. Resolution samples its observation grid; it does not make candles. Use this to migrate v1 instrument history. |
| Retained chains — `market.snapshot_history` | `/market/snapshot-history?series=options` | Legacy snapshot observations, including older archive generations. One timestamp can contain many instruments; page limits count timestamps. |
| Retained positioning series — `market.positioning_history` | `/market/positioning-history?series=...` | Legacy OI, volume and positioning totals sampled at observation times: by strike, put/call ratios, max pain, venue and currency totals, and OI/volume by expiry (`options_oi_maturity`, `options_volume_maturity`, `options_oi_volume_maturity`). Sampled state, not interval aggregates or trades. Use these to migrate `/historical/options/...` totals. |
| Modern ticker state — `options.ticker_history` | `/options/ticker-history` | Final observed state per interval: premiums, bid/ask, sizes, IV, OI and rolling USD volume. No OHLC, Greeks or underlying reference. |
| Modern chain — `options.snapshot` | `/options/snapshot` | Instrument states at a retained minute, with joined candle fields. Not an original listing/trading-status timeline. |
| Premium candles — `options.ohlcvt` | `/options/ohlcvt` | Trade-derived premium OHLC supplemented by mark-filled minutes; interval trade volume/counts. A candle can exist without a trade. |
| Individual executions — `options.trades` | `/options/trades` | Retained trade events and available enrichments, not synthetic marks. |
| Retained strategy-grouped trades — `options.trade_groups` | `/options/trade-history/strategy-groups` | Legacy v1 grouped trade history: one row per second, venue, currency, strategy and block trade id with its trades, a sell-positive premium total, amount and notional totals and one trade's Greeks. Group pagination with optional block trade id filter and sort; not individual executions and not the per-trade retained route `/options/trade-history`. Use this to migrate `/historical/options/v2/trades`. |
| Instrument IV — `options.volatility` | `/options/volatility` | Mark/bid/ask IV OHLC, spreads and latest retained Greeks. Not the fitted/constant-maturity analytics family `/options/volatility-history`. |
| Best quotes — `options.level1` | `/options/level1` | Bid/ask price and displayed-size OHLC and summaries. Not full depth or quote-by-quote replay. |
| Outstanding positions — `options.open_interest` | `/options/open-interest` | Instrument OI OHLC, not an interval sum or trading volume. |
| Volume — `options.volume` | `/options/volume` | Latest rolling 24-hour ticker volumes, alongside interval trade volumes/counts. Keep these two types of volume separate. |
| Pricing references — `options.reference_price` | `/options/reference-price` | Mark-price OHLC and scalar final spot-index/underlying references; not index-price OHLC. |
| Current instrument discovery — `options.catalog` | `/options/catalog` | Recently observed, unexpired instruments. Not every instrument ever traded. |
| Historical instrument discovery — `options.history_discovery` | `/options/instrument-history/discovery` | Contracts observed on a requested UTC day, including contracts now expired; a separate exact-contract lookup returns whole-history observation bounds. Covers `options.instrument_history` only. |

Historical state rows cannot reconstruct genuine intraminute highs/lows or
trades that were never stored. Do not relabel legacy snapshots as trade candles.
Synthetic mark candles remain supported; they are not evidence of executions.

### Find and retrieve an expired option contract

Use an active subscription with `X-API-Key`; MCP `get_options_history_discovery`
accepts the same parameters and returns the same fields. Supported venues are
Deribit, OKX, Binance, Bybit and Paradex.

Here `currency` means the exact contract-family prefix before the first hyphen:
`BTC`, `BTC_USDC` and `BTC_USD` are separate searches, not one combined base-asset
request. For example, use `BTC_USD` for retained Paradex BTC option contracts.

1. Search a UTC day with `mode=instruments`, `exchange`, `currency` and `date`.
   For example:
   `GET /api/v1/options/instrument-history/discovery?mode=instruments&exchange=deribit&currency=BTC&date=2021-02-10&limit=100`.
   Follow `meta.next_cursor` until null, keeping the other parameters unchanged.
2. Copy an `instrument_name` and use `mode=coverage` to inspect all its retained
   dates, for example:
   `GET /api/v1/options/instrument-history/discovery?mode=coverage&exchange=deribit&instrument_name=BTC-10FEB21-35000-C`.
3. Send the result's `retrieval.parameters` to its `retrieval.path` to retrieve
   values. Those parameters use explicit bounds and `resolution=native`.
   Follow the history response's `meta.total_pages`; discovery and history have
   separate pagination. A long-lived contract can require narrower windows.

`meta.bounds_scope=requested_utc_day` is only the searched day's first/last
observation for each contract; `whole_instrument` is the exact contract's full
retained range. Neither proves that every intermediate minute or every contract
is present. No result means no matching observations were found; an error does
not mean absent history. A contract's absence from today's catalog does not
prevent historical retrieval.

`meta.dataset_id=options.instrument_observations` matches the linked history
response. `meta.definition_id=options.instrument_history` identifies its field
definitions and inventory entry; these existing identifiers are unchanged.

`native_interval_seconds=null` means no single native frequency is certified.
`supported_resolutions` lists UTC sampling choices, not guaranteed density:
requesting `1m` cannot create missing minutes, and `1h` does not create candles.
Dates use the observation labels of `options.instrument_history`, not modern
ticker bucket labels or proof of when information first became available.
Coverage metadata does not grant access to historical values beyond your plan.
This lookup does not join ticker history, trade candles or archived chains into
one dataset, and does not certify that a contract was tradable at any instant.

## Which spot product should I use?

| Product and definition ID | Route | What one row means |
| --- | --- | --- |
| Trade candles — `spot.ohlcvt` | `/spot/ohlcvt` | Price OHLC, VWAP, base and quote volume with buy/sell split and trade counts. Some stored minutes are flat zero-volume candles without trades, and minutes are not continuous: a candle is not evidence of an execution. |
| Ticker bars — `spot.ticker` | `/spot/ticker` | Genuine OHLC of last price, best bid/ask, spread and displayed sizes, plus rolling 24-hour state from the bucket's final minute. Trade-only minutes report bid/ask 0; use level1 for quote statistics. |
| Best quotes — `spot.level1` | `/spot/level1` | The ticker field set restricted to minutes with at least one quote update. Not full depth or a quote replay. |
| Volume — `spot.volume` | `/spot/volume` | Rolling 24-hour base/quote counters next to interval buy/sell volume and counts. Keep the two kinds of volume separate. |
| Cross-pair chain — `spot.snapshot` | `/spot/snapshot` | Ticker-state rows for one exchange at a minute or bucket. Trade-candle fields are not returned. |
| Individual trades — `spot.trades` | `/spot/trades` | Every stored trade (not a large-trade feed), rolling 30-day retention. |
| Aggregated book depth — `spot.l2_orderbook` | `/spot/l2-orderbook` | Per-interval statistics of top-10/20/50/100 liquidity, imbalance and microprice over stored book snapshots. |
| Raw book snapshots — `spot.l2_orderbook_raw` | `/spot/l2-orderbook-raw` | Individual stored snapshots with up to 100 levels per side, rolling 30-day retention. |
| Current discovery — `spot.catalog` | `/spot/catalog` | Pairs with a ticker row in the last 24 hours. Not a historical listing record. |
| Candle bounds — `spot.metadata` | `/spot/metadata` | First/last stored candle minute and row count for one pair. Candles only; not ticker, trade or book availability. |
| Retained v1 candles — `spot.retained_candles` | `/spot/candle-history` | Legacy spot OHLC with base/quote volume from a separate, older source. Use it to migrate `/historical/spot/...`. Not the same dataset as `spot.ohlcvt`. |
| Historical discovery — `spot.history_discovery` | `/spot/candle-history/discovery` | Historical pair names on a UTC day, or dated candle bounds for one pair at a required resolution. Covers `spot.retained_candles`, not modern spot OHLCVT. |

Spot-specific conventions:

- Prices are quote currency per base unit (USDT for `BTCUSDT`, USD for
  `BTC-USD`). `volume` is base asset; `quote_volume` is quote asset. USDT/USDC
  are not certified USD.
- Older `volume_quote` is not universally a sum of executed trade notionals.
  Coinbase, Kraken and Hyperliquid candles can carry an estimate: base volume
  multiplied by that candle's close. See the [spot comparison](#compare-the-two-spot-candle-products)
  before combining it with modern `quote_volume`.
- `price_change_24h_pct` is a **fraction**: `0.0235` means +2.35%.
- Spread fields are absolute quote-currency differences, not basis points.
- Trade `bid_price`/`ask_price`/`bid_size`/`ask_size` are the same venue's best
  bid/ask and displayed sizes from its ticker stream as of the trade, refreshed
  every few seconds: a near-time reference rather than the exact book at
  execution. Use level1 or the order-book products for execution-level analysis.
- Trade `direction` is the aggressor (taker) side: `buy` took the ask, `sell`
  hit the bid. Candle `buy_*`/`sell_*` fields split activity the same way.
- Spot trade and raw-book `date` values are ISO 8601 truncated to the second;
  use `timestamp` (Unix milliseconds) for ordering.

### Find and retrieve a historical spot pair

Use `/api/v1/spot/candle-history/discovery` with an active subscription and
`X-API-Key`. MCP `get_spot_history_discovery` accepts the same parameters and
returns the same envelope. Both modes require `exchange` and `resolution`:
daily availability does not establish minute availability.

1. Find pairs with daily candles on a historical UTC day:
   `GET /api/v1/spot/candle-history/discovery?mode=instruments&exchange=coinbase&resolution=1d&date=2017-01-01&limit=100`.
   Follow `meta.next_cursor` with the same exchange, resolution, date and limit.
2. Inspect one pair's dated bounds at that resolution:
   `GET /api/v1/spot/candle-history/discovery?mode=coverage&exchange=coinbase&resolution=1d&instrument_name=btc-usd`.
   Repeat with `resolution=1m` to inspect minute history separately. Coverage
   does not accept `date`, `limit` or `cursor`.
3. Copy a result's `retrieval.path` and `retrieval.parameters` to request
   `/api/v1/spot/candle-history`. The example covers the entire first dated UTC
   day, not the whole history. Follow the candle response's `meta.total_pages`;
   its pagination is separate from discovery. Do not shorten the example's end
   to a daily candle's midnight label: that would clip the candle's inputs.

Venue and pair letters are lowercased; pair separators and aliases are not
combined. Copy the returned historical pair name even if it is absent from
today's catalog. `meta.bounds_scope=requested_utc_day` applies to instrument
searches; `whole_instrument` applies to coverage at the requested resolution.
Both `meta.dataset_id` and `meta.definition_id` are `spot.retained_candles`.

`first_observed_at` and `last_observed_at` exclude timestamp zero. If matching
candles include timestamp zero, `has_zero_timestamp=true`; it does not mean
coverage began in 1970. A pair with only zero-dated candles has null bounds and
null retrieval. Existing historical candle responses are unchanged. An empty
successful lookup means no matching candle was found; a failed lookup returns
an error instead of empty coverage.

From 2024-01-01, dates label UTC candle starts. Earlier dates preserve their
published labels; their start-versus-close convention is not separately
verified. Bounds are not candle close times, listing or delisting times, proof
of original availability, or guaranteed uninterrupted coverage. Current-day
candles may be incomplete. Discovery shares the history request allowance,
does not expand access to values beyond your subscription, and has no x402
access.

## Which perpetual or futures product should I use?

Each product exists for both families: `perpetuals.*` under `/perpetuals/...`
and `futures.*` (dated futures only) under `/futures/...`.

| Product | Definition IDs | What one row means |
| --- | --- | --- |
| Trade candles | `perpetuals.ohlcvt`, `futures.ohlcvt` | Price OHLC, VWAP, interval volume, trade counts and liquidation/block volume. Includes synthetic mark-filled candles (zero volume, OHLC = mark); `trades_count = 0` identifies them. `mark_price`/`index_price` are final-minute values, not OHLC. |
| Ticker state | `perpetuals.ticker_history`, `futures.ticker_history` | One final observed state per interval: mark, index, best bid/ask and sizes, spread, OI, rolling USD volume (perpetuals add funding; futures add days to expiry). Not OHLC. |
| Open interest | `perpetuals.open_interest`, `futures.open_interest` | OI OHLC for one instrument (instrument required). Outstanding quantity, not traded volume. |
| Funding and basis | `perpetuals.carry`, `futures.carry` | Perpetuals: funding OHLC and basis. Futures: basis and annualized basis carry. |
| Best quotes | `perpetuals.level1`, `futures.level1` | Bid/ask price and displayed-size OHLC, spread and total liquidity; only minutes with quote updates. |
| Volume | `perpetuals.volume`, `futures.volume` | Rolling 24-hour state next to interval trade volume and counts (instrument required). |
| Pricing references | `perpetuals.reference_price`, `futures.reference_price` | Mark and index OHLC. Minutes with a missing or non-positive reference are omitted, leaving gaps. |
| Chain | `perpetuals.snapshot`, `futures.snapshot` | Instrument states at a minute or bucket with joined candle fields. |
| Individual trades | `perpetuals.trades`, `futures.trades` | Stored trade events, rolling 30-day retention. |
| Trade aggregates | `perpetuals.trades_summary`, `perpetuals.flow`, `futures.trades_summary`, `futures.flow` | Grouped statistics over a window of stored trades (30-day retention). Totals across instruments can mix quantity units. |
| Liquidations | `perpetuals.liquidations`, `futures.liquidations` | Individual forced-liquidation events with base and USD amounts. |
| Aggregated book depth | `perpetuals.orderbook`, `futures.orderbook` | Per-interval liquidity, imbalance and microprice statistics over stored book snapshots. |
| Raw book snapshots | `perpetuals.orderbook_raw`, `futures.orderbook_raw` | Individual stored snapshots, rolling 30-day retention. |
| Current discovery | `perpetuals.catalog`, `futures.catalog` | Recently observed instruments (futures: also unexpired). Expired or delisted instruments remain requestable by name. |
| Historical discovery | `perpetuals.history_discovery`, `futures.history_discovery` | Exact contract names observed on a UTC day, or observation bounds for a currency and exact contract. Covers the corresponding `instrument-history` product, not modern ticker history. |
| Retained v1 observations | `perpetuals.instrument_history`, `futures.instrument_history` | Legacy per-instrument observations (price, index, basis, yield, OI, volume, quotes; perpetuals add funding). Use these to migrate `/historical/derivs/...`. |
| Retained funding and carry series | `derivatives.funding_history` | Legacy cross-venue funding, yield, OI-weighted funding/basis and realized-volatility series. |
| Retained chains | `market.snapshot_history` | `series=futures` or `series=perpetuals` archive snapshots (also options series). |
| Retained positioning series | `market.positioning_history` | Legacy futures/perpetual OI and volume by margin, venue and total (`futures_*` series), sampled at observation times. Use these to migrate `/historical/futures/total_*`. |

### Find and retrieve a historical derivative contract

Use `/api/v1/perpetuals/instrument-history/discovery` or
`/api/v1/futures/instrument-history/discovery` with an active subscription and
`X-API-Key`. MCP `get_derivatives_history_discovery` uses the same parameters
plus `instrument_type=perpetual` or `future`, and returns the same fields.

1. Find names observed on a UTC day, for example:
   `GET /api/v1/futures/instrument-history/discovery?mode=instruments&exchange=deribit&currency=BTC&date=2026-09-22&limit=100`.
   Follow `meta.next_cursor` until null, keeping the other selectors and limit.
2. Copy the exact case-sensitive name and inspect its bounds, for example:
   `GET /api/v1/futures/instrument-history/discovery?mode=coverage&exchange=deribit&currency=BTC&instrument_name=BTC-25SEP26`.
3. Send `retrieval.parameters` to `retrieval.path`. This sample request covers
   only the first observed UTC day within the bounds, not the entire history.
   Follow that response's `meta.total_pages` and choose explicit small windows
   to retrieve more dates. Discovery and history have separate pagination.

Both modes require `currency`: the exact historical asset label, such as `BTC`.
Unlike option discovery's contract-family prefix, derivative `currency=BTC`
can include inverse and linear contracts, each under its own name. Different
currency labels are not combined or inferred from symbols. Keep contract-name
case unchanged: `PF_XBTUSD` and `pf_xbtusd` are separate history selectors, not
proof of two distinct economic contracts. Do not add their open interest together
merely because both names appear.

`bounds_scope=requested_utc_day` covers only the searched day.
`bounds_scope=currency_instrument` covers the exact currency and contract over
all retained dates under that label; it excludes any history under another
currency label. The linked value route selects the exact contract over the
returned dates without a currency filter. These bounds do not certify continuous
history, a listing/delisting time, or the first public availability of a value.
An error means the lookup failed, not that history is absent.

The corresponding `meta.dataset_id` and `meta.definition_id` identify the
existing instrument-history product. Its timestamps and quantities are not
converted to modern ticker conventions: `native` preserves observation labels;
coarser resolutions select UTC grid observations, not candles. No universal
one-minute timestamp shift is applied. `native_interval_seconds=null` means no
single native cadence is certified, and bounds do not grant additional access.
Modern ticker/candle bounds still require their own product-specific lookup.

### Quantity units differ by venue

No response field states the quantity unit. Verified conventions:

| Venue / instrument type | Price | Candle volume, displayed sizes, book liquidity | `oi` on OI/ticker/snapshot | Trade `amount` |
| --- | --- | --- | --- | --- |
| Deribit inverse (`BTC-PERPETUAL`, `BTC-25DEC26`) | USD | USD | Base coin (BTC) | USD |
| Deribit USDC linear (`BTC_USDC-PERPETUAL`) | USDC | Base coin | Base coin | USDC notional |
| Binance USDT-margined (`BTCUSDT`, `BTCUSDT_261225`) | USDT | Base coin | Base coin | USDT notional |
| Binance coin-margined (`BTCUSD_261225`) | USD | Contracts ($100 face for BTC) | Base coin | Not verified |

Other venues are not yet verified; check the instrument's contract convention
before combining venues. Trade-level `open_interest`/`oi_change` follow the
trade-amount convention, so they are not comparable with the `oi` field above.

### Funding and carry

- Modern funding fields are **decimals**: `0.0001` means 0.01%. `funding_rate`
  is the venue's native per-interval rate (hourly on Hyperliquid, 8-hourly on
  Binance); `funding_8h` normalizes it to 8 hours. Deribit inverse
  `funding_rate` has been observed at 0 while `funding_8h` was non-zero; its
  per-interval meaning is not verified, so prefer `funding_8h` for comparisons.
- Perpetual `annualized_carry` = `funding_8h_close × 3 × 365 × 100` (percent,
  simple). Dated-futures `annualized_carry` = `basis_close / index ×
  365 / days_to_expiry × 100`.
- `basis_open`/`basis_close` are observed mark minus index. `basis_high` =
  mark high − index low and `basis_low` = mark low − index high are bounds, not
  an observed basis.
- Retained funding (`*.instrument_history`, `derivatives.funding_history`) is
  documented in **percentage points**, 100 times the modern decimal. Convert
  before joining retained and modern series.

### Bucket bounds for open interest and carry

Most modern minute products filter source minutes before aggregation (see
below). Open interest and carry instead compare each bucket's **start** with
`start`/`end`: a bucket is included when its start lies in the window, so an
unaligned `start` drops its containing bucket and an `end` inside a bucket
includes the whole bucket. At `1m` both rules are identical. The carry route
labels buckets with `timestamp` (Unix milliseconds) and `minute` (ISO), not
`date`. Derivatives trade `date` is `YYYY-MM-DD HH:MM:SS.mmm` UTC text, not
ISO 8601; use `timestamp` for ordering.

## Comparing repeated historical reads

This guidance applies to `/api/v1/futures/ticker-history`,
`/api/v1/perpetuals/ticker-history`, `/api/v1/futures/orderbook`,
`/api/v1/perpetuals/orderbook` and `/api/v1/spot/l2-orderbook` on the v2 host.

The returned `data` rows and the surrounding `meta` answer different questions.
Coverage and completeness describe current series availability, not a revision
of the requested historical window. In particular,
`meta.coverage.latest_observed_at`, `meta.completeness.complete_through` and
`meta.completeness.updated_at` can change while every returned data row stays
unchanged. Therefore a changed whole-response checksum or ETag does **not**,
by itself, establish that historical values changed. ETag behavior is unchanged.
There is no per-window data revision field on these responses.

For a data-only comparison:

1. Keep the endpoint, exchange, instrument, explicit UTC `start`/`end`,
   resolution, filters and sort order the same. An omitted time bound can move
   between requests.
2. Check HTTP success and availability/error information. Follow each read's
   complete `meta.next_cursor` sequence without changing its bounds or filters.
   A failed, truncated or incomplete sequence cannot establish an unchanged
   window. Keep the original pages and metadata for audit. A multi-page read is
   not an atomic snapshot.
3. Concatenate the `data` arrays in received order. Compare **all** row keys and
   values, including `date`, identifiers and any product-specific fields—not
   only prices or the fields named by one correction. Preserve null versus
   missing, zero versus null, row order, duplicate rows and row count. Normalize
   JSON object-key order; page boundaries need not be part of the comparison.
4. Compare metadata separately. If you use a digest, hash the resulting
   data-only representation with the same algorithm on both reads and retain
   the request parameters alongside it. REST and MCP are not byte-equivalent:
   MCP can omit fields and round numbers. Use REST for this comparison.

For example, after validating and collecting **all** successful REST pages:

```js
function canonicalJson(value) {
  if (Array.isArray(value)) {
    return '[' + value.map(canonicalJson).join(',') + ']';
  }
  if (value !== null && typeof value === 'object') {
    return '{' + Object.keys(value).sort().map(
      key => JSON.stringify(key) + ':' + canonicalJson(value[key])
    ).join(',') + '}';
  }
  return JSON.stringify(value);
}

function historicalDataJson(pages) {
  if (!Array.isArray(pages) || pages.length === 0 || pages.some(
    page => !page || page.success === false || !Array.isArray(page.data)
  )) {
    throw new Error('Expected a complete sequence of successful REST pages');
  }
  return canonicalJson(pages.flatMap(page => page.data));
}
```

The example does not fetch pages or certify completeness; callers must perform
the checks above. An unchanged comparison describes those two reads only.
Historical corrections and availability changes remain possible. Neither a
bucket timestamp nor this comparison certifies when a value first became
available for a live decision.

### Ticker correction field-name clarification

`HIST-2026-08-19-V2-TICKER-DETERMINISM` used the names in the left column below.
The existing ticker-history responses use the names on the right. These are
the public fields covered by that correction; this is **not** a response-field
rename or a new numerical correction.

| Name in the earlier notice | Public response field |
| --- | --- |
| `mark_price_close` | `mark_price` |
| `index_price_close` | `index_price` |
| `bid_price_close` | `bid_price` |
| `ask_price_close` | `ask_price` |
| `bid_size_close` | `bid_size` |
| `ask_size_close` | `ask_size` |
| `bid_ask_spread_close` | `bid_ask_spread` |
| `oi_close` | `oi` |
| `funding_rate_close` | `funding_rate` (perpetuals only) |

`volume_usd_24h` keeps its name on both routes. `days_to_expiry` belongs to
dated futures; `funding_rate` and `next_funding_time` belong to perpetuals.
There is no dated-futures funding field to re-collect. No new re-collection is
required solely because of this clarification; if the old names prevented
applying the original notice, use these public names with its existing scope.

The earlier L2 notice, `HIST-2026-08-04-L2-DETERMINISM`, covers
`bid_liq_{depth}_{stat}`, `ask_liq_{depth}_{stat}` and
`imbalance_{depth}_{stat}` for depths `10`, `20`, `50`, `100` and statistics
`open`, `high`, `low`, `close`, `avg`, together with `microprice_{stat}` and
`snapshot_count`. This clarifies the phrase “All aggregated L2 fields”; it does
not change values, historical coverage or retention. For data comparison,
preserve every other returned row field too.

## How far back does history go?

There is no single “Laevitas history starts on” date. Coverage depends on the
dataset, exchange, currency, instrument and source generation. A modern ticker
boundary does not bound retained instrument history or older chain archives.
An active catalogue's oldest expiry does not bound any historical product.

Start with the [public historical inventory](data-inventory.md), also available
as [JSON](data-inventory.json), `GET /api/v1/changelog/data-inventory` and MCP
`get_data_inventory`. It separates older instrument observations, modern candles,
archived chains and downloadable tick files. Each date states its selector,
precision and evidence; sampled or approximate dates are not exact first minutes.
The inventory is dated, not a live completeness report. Its listed gaps are not
an exhaustive gap audit.

For a modern instrument, ask for its exact product and selector:

```http
GET /api/v1/changelog/data-coverage?endpoint_id=options.volatility&exchange=deribit&instrument_name=BTC-2NOV25-110000-C
X-API-Key: <your key>
```

Interpret the response as follows:

- `earliest_observed_at` / `latest_observed_at` bound stored rows for that
  product/selector. They do not promise a row at every intermediate minute.
- `completeness.status=unavailable` or `not_yet_certified` means completeness
  is not certified. It does not by itself mean no data exists.
- `retention=null` means that response declares no rolling retention term,
  **not** that data will live forever. Proposed lifecycle changes are not
  effective retention limits; announced terms and their scopes apply.
- Coverage, subscription entitlement and export availability are separate.
  A verified archive/export entry is not automatically an anonymous download.

For retained routes whose definition has `coverage_endpoint_id=null`, this
live discovery endpoint does not provide whole-source bounds. The historical
inventory supplies reviewed dates and retrieval routes for a subset of these
products, without changing that live contract. Inspect the requested
retained windows and their availability metadata; page bounds are not archive
bounds. For a full-history export, request a selector-specific inventory and
sample before assuming a multi-year range is complete. Do not infer missing
old history from a modern endpoint or current catalogue alone.

## What does the timestamp mean?

Modern minute products (options, spot, perpetuals and futures) use UTC interval-start labels. A row labelled
`2025-11-01T15:09:00Z` describes the bucket `[15:09:00, 15:10:00)`, not a quote
captured at exactly 15:09:00. `1m` means minute data, **not tick-level data**.

| Product | At a coarser resolution |
| --- | --- |
| Ticker history | Final observed minute state; OI and rolling volume are not summed. |
| OHLC products | First open, maximum high, minimum low, last close across included minute bars. OHLCVT interval volumes/counts sum. VWAP weights by traded volume; zero-volume buckets retain the mean minute reference price. |
| Retained instrument history | Samples the UTC observation grid; a daily observation is not a daily candle. |
| Individual trades | Event timestamps, not buckets. `timestamp` is Unix milliseconds; `date` is UTC ISO text. |

Modern time-series `start` and `end` filter included source minutes **before**
aggregation and are inclusive. An omitted `start` defaults to 24 hours before
`end` (or before now when `end` is omitted). Use complete aligned windows for comparable
candles: for 15:00–16:00 use `start=15:00:00Z&end=15:59:59Z`, not an inclusive
end at 16:00:00. A partial edge bucket can legitimately change when the window
changes. Keep bounds fixed while following `meta.next_cursor`.

Retained instrument `date` remains Unix **milliseconds**, with its legacy
observation label preserved. Some investigated legacy/modern observations align
after a one-minute shift, but that is not a universal cross-venue/generation rule.
Do not shift an entire history without a scoped comparison. Compatibility aliases
keep legacy date-only boundary behavior; native retained routes take explicit
UTC instants. See the field guide for family-specific exceptions.

### Snapshot date is not always an as-of cutoff

At `resolution=1m`, modern `/options/snapshot?date=...` selects an **exact
retained minute**. Use `:00` seconds. There is no nearest-minute fallback.
Omit `date` to select the latest matching minute.

At a coarser REST resolution, `date` selects the **whole containing bucket**,
including observations later than that instant. For example, a 15:09 date in
a 1h request selects the 15:00–16:00 bucket. It is not a causal snapshot as of
15:09. Use the one-minute product when selecting a historical minute.

### Can I use the 15:09 row for a decision at 15:10:00?

Not as a certified availability assumption. These responses do not expose an
original publication timestamp or an as-originally-published version
selector. Minute products also do not expose their constituent exchange-event
timestamps. Bucket completion alone does not prove receipt by the next boundary.
No measured or guaranteed publication-lag bound is supplied by these definitions.

Historical responses are the current retained/corrected view. Later corrections
or additions can change values or which observations are available. Check the
corrections/status feeds; they are not a publication-time event log for every row. A `computed_at` inferred
from the bucket label would not recover the missing original availability time.
For strict causal research, retain your own receipt-time observations or obtain
an independently verified point-in-time source; a current historical response
does not provide that certification.

## Units and missing values

- IV is in **percentage points**: `65` means 65%, not `0.65`. IV spreads are
  volatility-point differences. Do not multiply these IV fields by 100 again.
- Premiums follow the instrument price currency: BTC/ETH for inverse Deribit
  options versus stablecoin/USD for linear products. Separate the books with
  settlement/margin filters; do not add the prices without conversion.
- Quantities retain source conventions; these readers do not apply an extra
  contract multiplier. OI/displayed size/trade amount are not universally USD
  or “one contract = one coin.” Check the instrument's contract convention.
- `index_price` means spot index. `underlying_price`, where served, means the
  option pricing reference, potentially expiry-specific. They are not substitutes.
  OHLCVT deliberately has no `underlying_price` field; use reference-price.
- Greeks retain venue conventions; a field name does not certify identical
  theta/vega/rho scaling across exchanges.
- `null`, numeric zero, an omitted field and an absent row differ. Do not turn
  an absent venue into zero OI or an absent minute into a synthetic execution.

Legacy archive generations can have different mappings. In retained option
snapshots, the intermediate generation from 2025-02-24 to before 2025-08-20
preserves `underlyer_spot` as stored underlying and `forward_price` as stored
index; the recent generation uses spot index and underlying respectively.
See [Market snapshots](history-field-guide.md#market-snapshots) before stitching
those fields together. Migrating their URL does not change those legacy values.

## Instrument universe and MCP presentation

Modern historical snapshot membership comes from matching retained ticker
rows, not today's active catalogue. Metadata alone does not invent a row.
However, this is not a certified record of what was originally listed, active,
unexpired and open for trading at each instant; a later repair can change the
historical membership. No original listing/trading-state timeline is provided
by these products. An expired instrument can still be requested directly.

REST `/options/snapshot` returns the matching chain. MCP `get_options_snapshot`
uses 1m only and returns at most `limit` instruments (100 default, 1000 maximum).
Inspect `total`, `count` and `truncated`; there is **no continuation cursor for
that truncated chain**. Use REST for the full chain, not a larger-than-supported
MCP limit. Snapshot OI is named `oi`; this response does not include Greeks or
`underlying_price`.

Modern options MCP tools compact rows: repeated identifiers, null/empty fields
and selected optional zero fields may be omitted; long fractional numbers may
be rounded to 10 significant digits. Snapshot time moves to `snapshot_time` in
the envelope. Use REST for exact numeric/field/null comparisons. Retained-history
MCP tools preserve their native response contracts instead. Neither formatting
choice changes the underlying product definition.

## Reproduce a v1/v2 comparison

First compare **the same retained dataset** using the mapped v2 compatibility
path, not `/options/ohlcvt` as a substitute for legacy state history.
Download the [bounded options comparison plan](options-comparison.example.json),
[route mapping](legacy-history-routes.json) and [read-only verifier](verify-history.mjs).
Use a subscription entitled to both routes; the verifier is not a paid x402 flow.
Keep credentials in the environment, never in the plan or report.

```sh
node verify-history.mjs --cases options-comparison.example.json
# After reviewing the dry-run plan, with LAEVITAS_API_KEY set securely:
node verify-history.mjs --cases options-comparison.example.json --live
```

The example selects `BTC-2NOV25-110000-C` on 2025-11-01, with a narrow and a
wider window, different page sizes and spaced repeats. It walks pages and
reports row/field differences instead of hiding them with a timestamp shift.
Checked on 1 October 2026: both shapes matched across all pages and both rounds
(16 requests, four and six rows per shape, no response-value differences).
Keep `legacy-history-routes.json` beside the downloaded verifier. The report is
written to stdout; save it locally if needed. No credentials are included.

Perpetual and dated-future instrument history have equivalent bounded plans:
[perpetuals](perpetuals-comparison.example.json) (Deribit `BTC-PERPETUAL`) and
[futures](futures-comparison.example.json) (Deribit `BTC-25SEP26`, an expired
contract). Run them the same way, for example
`node verify-history.mjs --cases perpetuals-comparison.example.json --live`.
Their windows match ones that passed a page-by-page v1 parity check on
22 September 2026; that sample does not certify other windows or venues.

The [spot migration plan](spot-comparison.example.json) checks the same older
spot dataset on the v1 host and its mapped v2 path. Run
`node verify-history.mjs --cases spot-comparison.example.json --live` with
the route mapping beside the script. Its Binance BTC/USDT 1m and 5m requests
matched across all pages and both spaced rounds on 11 October 2026 (24
requests). This verifies those request samples, not equality with OHLCVT.

For the separate modern-product comparison, request the same instrument on
`/options/ticker-history`, `/options/volatility` and `/options/ohlcvt` with
`resolution=1m&start=2025-11-01T15:09:00Z&end=2025-11-01T15:11:59Z&limit=10`.
Compare only shared concepts: ticker `mark_iv` versus volatility `mark_iv_close`,
for example—not candle `close` versus ticker `mark_price` as an identity rule.
Record request times, exact parameters, every page, units and any scoped
timestamp normalization. A match on that sample proves that sample, not complete
history, identical granularity or causal availability across generations.

### Worked observation, checked 1 October 2026

For `deribit / BTC-2NOV25-110000-C / 2025-11-01T15:09:00Z / 1m`:

| Response | Observed value | Interpretation |
| --- | --- | --- |
| Ticker `mark_iv`; volatility `mark_iv_close` | Both `18.56` | Same final IV state, 18.56%, with no extra percent conversion. |
| Volatility `mark_iv_open` | `18.39` | IV changed within the minute; the timestamp does not mean the final value was known at its start. |
| OHLCVT `open/high/low/close` | All `0.0047` BTC; volume and trade count both zero | A mark-filled premium candle, not four executed prices. |
| Reference-price `mark_price_high/low` | `0.0048 / 0.0044` BTC | The observed mark-price range differs from the flat synthetic premium candle. For mark-price OHLC, use reference-price. |
| `index_price`; `underlying_price` on reference-price | `110280.09`; `110293.0224` | Spot index and option pricing reference are distinct. |
| Volatility coverage for this exact instrument | `2025-10-29T08:02:00Z` to `2025-11-02T07:59:00Z` | An instrument/product observation range, not the start/end of all Deribit history. |

These are recorded sample results, not pinned immutable values or a completeness
certificate. Re-run the stated requests to inspect the current retained view.

### Compare the two spot candle products

`/api/v1/spot/candle-history` and `/api/v1/spot/ohlcvt` are different datasets
on the v2 host. Moving an older request to the first route preserves that
product; replacing it with the second is a product change. Neither one is a
universal substitute for the other.

| Older candle field | OHLCVT field | Comparison rule |
| --- | --- | --- |
| `date` (Unix milliseconds) | `date` (UTC ISO text) | Convert the representation only. For history from 2024 onward both label UTC bucket starts. Do not shift a minute to force a match. Earlier candle start/close labeling is not independently verified and is outside this comparison. |
| `open`, `high`, `low`, `close` | Same four fields | Quote currency per base unit. Match venue, pair and complete bucket bounds. Same units do not guarantee identical observations. |
| `volume_base` | `volume` | Base quantity during the selected candle, not rolling 24-hour volume. Compare as returned, without a contract multiplier. |
| `volume_quote` | `quote_volume` | Same quote-asset units, but not always the same calculation. The older value can be base volume × close; OHLCVT sums trade quote amounts. |
| No equivalent | `vwap`, trade counts, buy/sell splits, trade IDs | Do not invent these from an older candle or assume its close equals VWAP. |
| Absent row or `null` | Absent row, `null` or numeric zero | Keep these distinct. A flat, zero-volume OHLCVT candle alone does not prove a trade occurred. |

For Coinbase, Kraken and Hyperliquid, some older quote-volume history uses
base volume multiplied by each candle's close. When minute candles are combined
into a longer period, these estimates are added; multiplying the resulting
total base volume by the final close is not equivalent. Other generations may
preserve a venue-reported quote volume or zero when unavailable. No field on
the older response identifies the calculation used for each row. Do not use
`volume_quote / volume_base` as an independently measured VWAP. The worked
Coinbase sample below demonstrates the close-price estimate, not a claim about
every venue or historical generation.

Before comparing:

- Choose the exact historical name from candle-history discovery and the
  corresponding OHLCVT pair name. State the base and quote assets explicitly;
  `btcusdt`/`BTCUSDT` is not a license to combine USD and USDT pairs or renamed
  instruments. Discovery bounds are product- and resolution-specific.
- Set `start`, `end` and `resolution` explicitly. Shared resolutions are `1m`,
  `5m`, `15m`, `1h` and `1d`; older `30m` and modern `4h` are not shared.
  The end is inclusive: a complete hour ends at `:59:59`, not the next hour.
  Both routes can return partial edge candles when a bound cuts a bucket.
- Follow all numbered pages for older candles and all `meta.next_cursor`
  pages for OHLCVT, keeping bounds fixed and requesting ascending order.
  Compare the data fields, not full-response hashes or changing coverage metadata.
- Use REST for exact comparison. Modern MCP can omit fields or round numbers;
  that formatting is not a different dataset.

Download the [spot product plan](spot-products-comparison.example.json) and
[spot product comparator](compare-spot-products.mjs). They need Node.js 22+
and an eligible subscription; they do not make x402 payments.

```sh
node compare-spot-products.mjs --cases spot-products-comparison.example.json
# After reviewing the dry run, with LAEVITAS_API_KEY set securely:
node compare-spot-products.mjs --cases spot-products-comparison.example.json --live
```

The comparator reads only the two named production routes, one request at a
time, with explicit page, request, response-size and time limits. It never
rounds away differences, converts units, moves timestamps or fills gaps. A
missing field, changed pagination total, HTTP error or exhausted budget makes
the comparison incomplete, not successful. Each case reports `sample_equal`,
`differences_observed` or `inconclusive`; the whole report is `review_required`
when differences are observed. Incomplete, inconclusive and review-required
reports exit with code 2. A matching sample is not a whole-history guarantee.

#### Recorded spot sample — checked 11 October 2026

All cases use 11 September 2026, `09:00:00` through `09:09:59` UTC:

| Pair and resolution | Observed result |
| --- | --- |
| Binance BTC/USDT, 1m | Both return 10 candles at the same timestamps. All four price fields match; some volumes differ at very small decimal places, so the exact comparison reports differences. |
| Binance BTC/USDT, 5m | Both return two candles. All four price fields match; base and quote volumes are not exactly equal. |
| Coinbase BTC/USD, 1m | Older candles return 8 minutes, OHLCVT returns 10. Prices match on the 8 shared minutes. The two additional minutes are 09:00 and 09:05 and have positive volume and trade counts; they are not zero-volume placeholders. |

At Coinbase `09:01`, the older `volume_quote` is `99113.61089467739` USD:
`1.28152077` BTC × the closing price `77340.62` USD/BTC. OHLCVT reports
`99103.48200546007` USD and the same base volume, with VWAP
`77332.71619582106` USD/BTC. Closing-price valuation and traded quote amounts
measure different things. At `09:03`, base volume also differs
(`4.25429396` versus `4.2542938` BTC); that small difference is not explained by
the quote-volume formula and remains unclassified. The cause of the two
missing older minutes has not been established by this comparison.

These are dated samples of the served view, not approved conversion rules or
proof that every difference is intentional. Use the older candle route when
preserving an existing older series. Evaluate OHLCVT independently when you
need its trade metrics; do not silently join the two products into one series.
