> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omnibook.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Series, assets, and categories

> Nine live series across BTC, gold, and WTI crude — how to list them, scope them, and price them.

Omnibook is no longer a single BTC book. **Nine recurring series run in
parallel**: three assets × three cadences. Each series is an independent order
book with its own rounds, its own strike, and its own price feed.

Two integers do all the work:

* **`category`** (1–9) identifies the **series**. It appears on every round
  object and every `rounds` WebSocket frame, and filters `GET /v1/rounds`.
* **`feed`** (0–2) identifies the **price source**. It scopes
  `GET /v1/oracle/price` and appears on every `oracle` WebSocket frame.

## The registry

| `category` | Series | Asset | Cadence | `feed` |
| - | - | - | - | - |
| 1 | BTC up/down · 60-second | BTC | 60s | 0 |
| 2 | BTC up/down · 5-minute | BTC | 5m | 0 |
| 3 | BTC up/down · 15-minute | BTC | 15m | 0 |
| 4 | Gold up/down · 60-second | XAU | 60s | 1 |
| 5 | Gold up/down · 5-minute | XAU | 5m | 1 |
| 6 | Gold up/down · 15-minute | XAU | 15m | 1 |
| 7 | WTI crude up/down · 60-second | WTI | 60s | 2 |
| 8 | WTI crude up/down · 5-minute | WTI | 5m | 2 |
| 9 | WTI crude up/down · 15-minute | WTI | 15m | 2 |

Several series share one feed: all three BTC cadences settle against feed 0.
Going the other way is unambiguous — a category maps to exactly one feed.

<Warning>
  **`category: 0` means "unclassified", not "all".**

  A round reports category 0 only in the gap before its slot's reference data
  lands — a `create` frame can arrive that way. It is never a wildcard, and
  `?category=0` filters *to* those unclassified rounds rather than matching
  everything. To ask for every series, **omit** the parameter.
</Warning>

<Note>
  The venue API speaks in integers: `market_id`, `round_number`, `category`,
  `feed`. Human tickers like `OB-XAU-UD-60S-260917-025111` are a display
  convention of the web app and are **not** fields on any venue endpoint. Do not
  parse them out of the API; derive your own labels from `category`.
</Note>

## Getting every live market

`GET /v1/markets?status=trading` returns live slots across **all nine series**
in one page — one market per series while every feed is up.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
GET /v1/markets?status=trading
```

<Warning>
  **The market object does not carry `category`.** A market row tells you the
  book (`best_yes_bid`, `tick_size`, `volume`), not which asset it is. Read the
  round to learn the series.
</Warning>

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
GET /v1/rounds/{market_id}   # -> category, strike, round_number, status
```

So the "what is trading right now, across everything" loop is two calls deep:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
CATEGORY = {
    1: ("BTC", 60),   2: ("BTC", 300),   3: ("BTC", 900),
    4: ("XAU", 60),   5: ("XAU", 300),   6: ("XAU", 900),
    7: ("WTI", 60),   8: ("WTI", 300),   9: ("WTI", 900),
}

live = request("GET", "/v1/markets?status=trading").json()["markets"]
for m in live:
    mid = m["market_id"]
    rnd = request("GET", f"/v1/rounds/{mid}").json()
    asset, cadence = CATEGORY.get(rnd["category"], ("unknown", 0))
    print(f"{asset} {cadence}s  market_id={mid}  round={rnd['round_number']}"
          f"  strike={rnd['strike']}")
```

Cache the `market_id` → `category` mapping for the life of the round only.
`market_id` is recycled, and a recycled slot can come back in a **different**
series. Re-read the round before acting on a cached mapping. See
[Rounds and markets](/concepts/rounds-and-markets).

## Getting one asset

`GET /v1/rounds` takes an optional `category` filter and returns that series'
rounds newest `round_number` first:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
GET /v1/rounds?category=4&limit=10     # gold 60-second
GET /v1/rounds?category=9&limit=10     # WTI crude 15-minute
```

A round enters this list when it **opens**, so within one category the first
entry is the most recently opened round for that series. That makes
`?category=N&limit=1` a usable "current round for this series" — but check
`status`, because it will read `frozen` or `settled` during the gap between
rounds, and the pre-created rounds waiting behind it are not listed yet.

<Warning>
  Without `?category=`, `GET /v1/rounds?limit=1` is **not** the live round. The
  list interleaves all nine series by `round_number`, so the newest row is
  whichever series opened last. This was already true with one series and is
  badly wrong with nine. Use `GET /v1/markets?status=trading` for live slots.
</Warning>

To follow one asset across all three cadences, accept its three categories
(BTC: 1, 2, 3 — gold: 4, 5, 6 — crude: 7, 8, 9). There is no "subscribe by
category": you pass `market_ids` on the per-market channels, and filter the
global `rounds` channel on `category` yourself.

## Prices, per asset

`GET /v1/oracle/price` takes an optional `feed`:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
GET /v1/oracle/price           # feed 0 — BTC (the default)
GET /v1/oracle/price?feed=1    # gold
GET /v1/oracle/price?feed=2    # WTI crude
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "price": "424518000000", "fresh_source_count": 1, "seq": "918243" }
```

* **Omitting `feed` means feed 0**, so every client written before multi-asset
  keeps its exact meaning.
* **The response does not echo the feed.** Track which feed you asked for. The
  WebSocket `oracle` channel *does* name it — see
  [Channels](/websocket/channels).
* `price` is an integer in units of 1e-8 USD, as a decimal string. The
  magnitudes differ by orders of magnitude across assets: BTC around
  `10000000000000`, gold around `424518000000`, crude around `6231000000`.
  Never infer the asset from the magnitude, and never hold these in a float.
* `fresh_source_count` is how many independent sources were live behind the
  median. BTC medians up to **five** CEX perp streams; gold and WTI each come
  from a **single** QFEX underlier, so `1` is normal and healthy for them. Do
  not hardcode a floor of 3.
* A `feed` that is not a u16 is rejected `400`. A valid-but-unknown feed is not
  an error — it answers the zero sentinel below.

## Market hours and dark feeds

BTC trades 24/7. **Gold and crude do not.**

| Asset | Session |
| - | - |
| BTC | Continuous, 24/7 |
| Gold (XAU) | Closed Friday 17:00 ET → Sunday 17:00 ET |
| WTI crude | Daily halt 16:00–17:00 America/Chicago, plus the weekend close |

When a session closes, the upstream feed stops publishing and the venue stops
opening new rounds for every series on that feed. The round that was already
trading freezes and settles normally; nothing is stranded.

<Note>
  **There is no session calendar on the API.** `GET /v1/exchange/schedule` covers
  venue maintenance windows, not market hours. The venue deliberately reacts to
  the feed actually being dark rather than trusting a hardcoded timetable, so it
  covers upstream outages as well as scheduled closes.
</Note>

The authoritative liveness check is the **zero sentinel** on the feed:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "price": "0", "fresh_source_count": 0, "seq": "0" }
```

`price: "0"` means "no valid tick" — closed session, upstream outage, or a
venue that has just restarted and not yet folded a tick. It is a normal `200`,
not an error, and no traded asset is ever really worth 0. Poll
`GET /v1/oracle/price?feed=N` and treat `price == "0"` as "this asset is not
opening rounds right now".

## WebSocket

The `oracle` and `rounds` channels are **global**. They are not per-market and
take no `market_ids`: one subscription receives every tick from every feed and
every lifecycle event from all nine series, interleaved. Scope them client-side
on the `feed` and `category` fields that each frame carries.

The per-market channels (`orderbook_snapshot`, `orderbook_delta`, `trades`) are
unchanged — you name the `market_id`s you want, and one subscription can span
markets from different assets.

See [Channels](/websocket/channels) for payloads and filtering examples.

## Related

* [Rounds and markets](/concepts/rounds-and-markets) — identifiers, settlement, recycling
* [Pauses and rounds](/concepts/pauses-and-rounds) — freeze, dark feeds, cancel-all
* [Channels](/websocket/channels) — `feed` and `category` on the global channels
* [Your first request](/quickstart/first-request) — the discovery calls end to end


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.