Data API conventions
What every data endpoint shares: authentication, values, lists, symbols, freshness and errors
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 <token>. 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 is404 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
…AtNsor…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:
limitsets the page size: 1 to 100, default 50. Larger values are reduced to 100, never refused.- To move, send
nextCursororprevCursorback ascursor, 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. sorttakes 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 or a USD turnover with no current rate.volume24his in base units andturnover24hin quote units, so onlyturnoverUsd24hcompares markets of different assets:sort=-turnoverUsd24hlists the biggest markets first.includeopts into extras, comma-separated. Every collection offerstotal, the exact count of matching items (nullotherwise). Instruments also offerfacetsandticker.facetscounts the matching instruments per venue, type, base asset and quote asset, each dimension under your filters minus that dimension’s own (venueId,venueandvenueId.notInare the venue dimension’s;search,statusandsymbolapply to every facet), so a filter panel keeps showing its alternatives: withvenueId=300&type=SPOT,venuescounts the spot markets of every venue andtypesevery type on venue 300, options included. The count of what you chose is its own entry:SPOTintypes,300invenues. Eachvalueis what the matching filter takes, with the display alias aslabel(nullfor types); values with no matches are omitted; within a dimension the values sort by count, most first, then by value, ids as numbers and type names as text.tickeris each row’s latest statistics, rendered from the same snapshot the statistics sorts read, so a page is ordered by the values it shows;nullfor an instrument that has never traded.- Filters are plain query parameters. Repeat a parameter, comma-separate its values, or both, for
“any of”:
venueId=300&venueId=301isvenueId=300,301. Different parameters narrow together:type=SPOT&venueId=300is the spot markets of venue 300. A parameter the resource does not accept is400 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) with400 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. Its schema says so: each item isanyOfthe documented values and any string. - The
.notInsuffix excludes instead:venueId.notIn=10matches every venue but that one. Where a resource offers ranges it documents.gteand.ltethe same way. searchis case-insensitive and resource-specific. Instruments match word prefixes: every word you send must be the start of one of the instrument’s own words, which are the words of its symbol (OKX@BTC/USDT:USDThasOKX,BTC,USDT), of the venue’s own symbol (BTC-USDT-SWAPaddsSWAP), of its type (PERPETUAL_SWAPhasPERPETUALandSWAP), its id, and, for a dated future, its expiry as a month, a year, a day and the month with a two-digit year (JUN,2027,25,JUN27). Sobtc usdtfindsOKX@BTC/USDT:USDT,okx btc perpthe OKX perpetual,1000the1000PEPEmarkets and every id starting1000, and one character is enough. Words are runs of ASCII letters and digits: anything else separates them and is otherwise ignored, sobtc/usdtisbtc usdt, and a search with no ASCII letter or digit is no search at all. Every match is listed; nothing is truncated. 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]]]:
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:
Branch on code, never on message. retryable says whether sending the identical request
again, after Retry-After or a short pause, can succeed with nothing else changed. Quote
requestId (also the X-Request-Id header) when you contact support. To name a request
yourself, send X-Request-Id with 8 to 128 of [A-Za-z0-9._-]: it becomes the request’s id,
and a value off that grammar is replaced with a fresh one. A conditional write’s
412 VERSION_CONFLICT adds currentVersion beside error; no operation here is one yet.
Codes are append-only: new ones will appear, and existing ones never change meaning.