> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.immix.xyz/guides/reference-data-guide/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.immix.xyz/_mcp/server. # Reference data guide The conventions every resource follows. The reference documents each resource; this page covers what they have in common. ## Authentication Every request carries a bearer token: `Authorization: Bearer `. A missing, expired or foreign token is `401 UNAUTHENTICATED`, with a `WWW-Authenticate` header naming the problem. ## Values * **Ids** are opaque strings, named after their entity: `venueId`, `instrumentId`. An id that does not exist is `404 NOT_FOUND`, whatever its shape. * **Numbers** — prices, quantities, amounts — are exact decimal strings such as `"0.0000001115"`. Parse them as decimals, never as floating point. The number of digits carries no meaning. * **Times** are epoch-nanosecond strings named `…AtNs` or `…TsNs`. * **Absent values** are always present, as `null`. * **Enums** are UPPER\_SNAKE and only ever grow. Tolerate values you don't know. ## Lists Every collection returns one page at a time: ```json { "items": [], "nextCursor": null, "prevCursor": null, "total": null, "asOfSeqTsNs": "1758640000123456789" } ``` * `limit` sets the page size: 1 to 100, default 50. Larger values are reduced to 100, never refused. * To move, send `nextCursor` or `prevCursor` back as `cursor`, **with the same filters and sort**. A cursor is bound to the query it came from. * A cursor the API cannot use — edited, expired by a key rotation, or sent with a different query — is `400 INVALID_CURSOR`. Drop it and fetch the first page again. * `sort` takes comma-separated properties; a leading `-` sorts descending. The order is always complete: ties are broken by the id, and absent values sort last, in both directions. On instruments, a statistic is absent for an instrument that has never traded, a 24-hour one while its window is still warming, and any the projection cannot state, such as a turnover it has no price to value. * `include` opts into extras, comma-separated. Every collection offers `total`, the exact count of matching items (`null` otherwise). Instruments also offer `facets` (counts per venue, type, base asset and quote asset, each counted under your filters minus that dimension's own, so a filter panel keeps showing its alternatives) and `ticker` (each row's latest statistics). * Filters are plain query parameters. Repeat a parameter, comma-separate its values, or both, for "any of": `venueId=300&venueId=301` is `venueId=300,301`. Different parameters narrow together: `type=SPOT&venueId=300` is the spot markets of venue 300. A parameter the resource does not accept is `400 MALFORMED_REQUEST`, so a misspelt filter never silently returns the unfiltered list. A filter value that matches nothing is an empty page: an unknown code or symbol, or an id that is not exactly one. A filter that takes an alias in place of an id (`venue=okx`, `baseAsset=BTC`) ignores case. An enum filter refuses a value it has never heard of (`type=CRYPTO`) with `400 MALFORMED_REQUEST`. It accepts every documented value and every value its rows carry, removed rows included, so a value you were served stays valid: enums only grow. * The `.notIn` suffix excludes instead: `venueId.notIn=10` matches every venue but that one. Where a resource offers ranges it documents `.gte` and `.lte` the same way. * `search` is case-insensitive and resource-specific. Instruments match **word prefixes** of the symbol's tokens, the venue's own symbol, the type and the id: `btc usdt` finds `OKX@BTC/USDT:USDT`, `perp` finds every perpetual swap. Venues match a **substring** of the code or display name; assets match a **substring** of the symbol. ## Symbols Every instrument has one canonical symbol, byte-identical to the symbol on the market-data stream, in the grammar `VENUE@BASE/QUOTE[:SETTLE[-DDMmmYY[-STRIKE-C|P]]]`: | Shape | Example | | -------------- | -------------------------------------------------------------- | | Spot | `OKX@BTC/USDT` | | Perpetual swap | `OKX@BTC/USDT:USDT` (linear), `OKX@BTC/USD:BTC` (inverse) | | Dated future | `BYBIT@BTC/USDT:USDT-25Jun27` | | Option | `BYBIT@BTC/USDC:USDC-26Sep26-100000-C` (a call; `-P` is a put) | `VENUE` is the venue code upper-cased; `SETTLE` is the asset the contract settles in; the date is the day, the month's three-letter name and a two-digit year; `STRIKE` is the strike price. A lookup by `symbol` is case-insensitive; the symbol served always uses this spelling. `GET /instruments?symbol=OKX@BTC/USDT&symbol=okx@btc/usdt:usdt-25JUN27` looks up to 100 symbols in one request; send `limit=100` to get a whole batch in one page. A symbol that names no instrument is simply absent from the page. More than 100 is `400 MALFORMED_REQUEST`, a symbol sent twice counting twice: split a longer list into batches. The request line and headers may run to 16 KB, room for a batch of the longest symbols and a bearer token. A dated product's maturity is `DDMmmYY` and nothing else. A name stored with the older `YYYYMMDD` maturity, such as `OKX@BTC/USDT:USDT-20270625` for `OKX@BTC/USDT:USDT-25Jun27`, finds nothing: convert it before looking it up. ## Freshness Reads carry the position of the event stream they reflect: the `X-Immix-As-Of-Seq-Ts` header, and `asOfSeqTsNs` in list pages. It is a sequencer timestamp in epoch nanoseconds, the same clock as `seqTs` on stream frames: every stream message at or before it is reflected in the response, so you can apply stream updates after it without gaps or repeats. A response built from several sources reports the oldest: an instruments page, which joins the catalogue with the latest statistics, reports the older of the two. When the position is unknown, the header is omitted and `asOfSeqTsNs` is `null`. ## Errors Every error uses one envelope: ```json { "error": { "code": "INVALID_CURSOR", "message": "The cursor is not valid for this request; drop it and fetch the first page again.", "retryable": false, "requestId": "5f0c6c1e-8a2b-4f7e-9c3d-2b1a0e9f8d7c" } } ``` Branch on `code`, never on `message`. `retryable` says whether the same request may succeed later. Quote `requestId` (also the `X-Request-Id` header) when you contact support. | Code | Status | Meaning | | ------------------------ | ------ | ------------------------------------------------ | | `MALFORMED_REQUEST` | 400 | A parameter is unknown, repeated or out of range | | `INVALID_CURSOR` | 400 | The cursor cannot be used with this request | | `UNAUTHENTICATED` | 401 | The bearer token is missing or not valid | | `NOT_FOUND` | 404 | No such resource | | `METHOD_NOT_ALLOWED` | 405 | The resource does not support this method | | `NOT_ACCEPTABLE` | 406 | Responses are `application/json` | | `RATE_LIMITED` | 429 | Slow down; `Retry-After` says when | | `INTERNAL_ERROR` | 500 | Something went wrong on our side | | `NOT_IMPLEMENTED` | 501 | The operation is published but not served yet | | `PROJECTION_UNAVAILABLE` | 503 | Reference data is briefly unavailable; retry | Codes are append-only: new ones will appear, and existing ones never change meaning. > How the reference data endpoints page, filter and sort, spell symbols, report freshness and answer errors