Export Schemas
An export schema defines how raw exchange data is transformed into structured columns in your exported CSV files. Each column maps to either system metadata (like timestamps) or a value extracted from the exchange’s JSON message.Built-in vs custom schemas
Every account has access to built-in schemas likeraw and normalized. Reference them by name when creating an export:
normalized is a shared name that covers most stream categories. When you reference normalized, the backend picks the version matching your datastream’s category automatically. Orderbook-specific built-ins book_5 and book_20 are also available for top-N snapshots.
The List export schemas endpoint returns all database-backed schemas (built-in normalized variants, book_5, book_20, and your custom ones). The special raw name is always available as a schema reference but is not returned by the list endpoint.
| Built-in schema | stream_category targets | Notes |
|---|---|---|
raw | all categories | Default. Two-column collection_timestamp_ns + raw JSON. Not returned by the list endpoint. |
normalized | trade, orderbook, book_update, quote, kline, liquidation | Referenced by name; backend picks the version matching your datastream’s category. No ticker version — use raw or a custom schema for that category. |
book_5 | orderbook | Top-5 depth snapshot. |
book_20 | orderbook | Top-20 depth snapshot. |
Built-in schemas are read-only.
DELETE /v1/export-schemas/{id} on a built-in returns 404 not_found — only custom schemas owned by your account can be deleted.Stream categories
Each schema is scoped to onestream_category. Built-in schemas have one version per category; custom schemas target a single category. Schema names are unique per (account, stream_category) pair — you can reuse the same name across different categories.
| Category | Describes |
|---|---|
trade | Executed trades |
orderbook | Full or top-N order book snapshots |
book_update | Incremental order book deltas (bid/ask level changes) |
quote | Best bid/ask quotes |
kline | OHLCV candles |
ticker | 24h rolling ticker summaries |
liquidation | Forced liquidations (derivatives) |
Exchange codes
Every entry in a schema’sdata and unfold maps is keyed by an exchange code. The List exchanges endpoint is the source of truth for which codes you can use.
| Code | Exchange |
|---|---|
binance | Binance |
bybit_linear | Bybit Linear (USDT/USDC perpetuals and futures) |
bybit_spot | Bybit Spot |
okx_spot | OKX Spot |
Schema structure
A schema has aname, a stream_category it applies to, a list of columns, and an optional unfold configuration:
output_column (the column name in output) and exactly one of:
meta— extract a system metadata valuedata— extract from the exchange’s JSON message, with per-exchange rules
A schema must have at least one column and by default can have up to 100. Contact support if you need a higher limit for your account.
Meta columns
Meta columns extract system-level values that are consistent across all exchanges:| Value | Description |
|---|---|
collection_timestamp_ns | When Ticksupply received the message (nanoseconds) |
format field:
| Format | Output | Example |
|---|---|---|
ns (default) | Nanoseconds since epoch | 1705312800000000000 |
us | Microseconds since epoch | 1705312800000000 |
ms | Milliseconds since epoch | 1705312800000 |
s | Seconds since epoch | 1705312800 |
iso8601 | ISO 8601 string | 2024-01-15T10:00:00Z |
Data columns
Data columns extract values from the exchange’s raw JSON message. Because different exchanges use different JSON structures, you provide extraction rules per exchange.Binance messages are stored with the exchange’s combined-stream envelope —
{"stream": "...", "data": {...}} — so Binance paths start with data.. Other exchanges use their own envelope shape; always match the raw message structure you see in the data stream.JSON extraction
Each exchange extractor has ajson field with:
path— dot-notation path into the JSON message (e.g.,"p","data.price","info.amount"). Positive integer segments are treated as 1-indexed array indices, so"data.1.price"picks the first element’spricefield.type— output data type
| Type | Use for | Example |
|---|---|---|
decimal(N) | Prices, quantities (N decimal places, 0 ≤ N ≤ 38) | decimal(18) |
f64 | Floating-point values like percentages | f64 |
i64 | Integer values like trade IDs | i64 |
string | Text values like side (“Buy”/“Sell”) | string |
bool | Boolean flags | bool |
decimal(N) values have 38 digits of total precision. The scale N (0 ≤ N ≤ 38) is how many of those digits sit after the decimal point — the rest are available for the integer part. decimal(18) leaves 20 integer digits, which fits any crypto price or quantity. At decimal(38) every value ≥ 1 overflows and is stored as NULL. Scales above 38 cause the export to fail.Transforms
When the raw value needs post-processing, add atransform — a SQL expression where {v} is the extracted value.
A common example: Binance represents trade side as a boolean (m = is the buyer the market maker?), while most downstream systems expect "buy" or "sell":
| Use case | Transform |
|---|---|
| Convert to lowercase | lower({v}) |
| Divide by 100 | {v} / 100 |
| Default for nulls | coalesce({v}, 0) |
| Extract substring | substring({v}, 1, 3) |
Unfold
Some exchanges pack multiple events into a single WebSocket message as a JSON array. For example, Bybit trade messages look like:data array becomes its own row — so the example above produces two rows.
Configure unfold per exchange by specifying the path to the array:
path accepts dot-notation for nested arrays (e.g., "result.trades") — point it directly at the array regardless of how deep it sits in the message.
Column path values stay the same whether or not unfold is configured — always use the full JSON path from the raw message. When unfold matches, the backend strips the unfold prefix before extracting from each array element.
For example, with unfold {"path": "data"} on Bybit Linear and a column "path": "data.p", the backend:
- Array-joins each element of
datainto its own row. - Strips the
data.prefix fromdata.p. - Extracts
pfrom each element.
| Unfold config | Column path | Extracts from each row |
|---|---|---|
| (none) | data.p | p inside the top-level data object |
{"path": "data"} | data.p | p inside each element of the data array |
{"path": "data"} | data | the whole array element |
Unfold only applies to exchanges that batch events. Exchanges like Binance that send one event per message don’t need unfold rules — just omit them.
Derive
Some exchanges split related values across multiple arrays in a single message. For example, Binance book-update messages carry separatebids (b) and asks (a) arrays:
bid/ask — you need the two arrays merged before unfold splits them apart. That’s what derive does: it computes a synthetic field on the raw message before column extraction runs, so unfold and column paths can reference it like any other field.
Configure derive per exchange. Each entry is a list of synthetic fields:
changes array where each element looks like {"side": "bid", "level": ["50000.00", "0.5"]} (or "ask"). The only operation currently supported is tagged_concat, which:
- concatenates every array in
arraysinto one, - wraps each source element under
value_field(herelevel), - tags it with the corresponding
tagundertag_field(hereside).
unfold at the synthetic field and let column paths extract from each element:
Column
path values for derived fields reference the synthetic field name directly (e.g., changes.side), not the original array path. The built-in normalized / book_update schema uses exactly this pattern — fetch it with Get export schema to see a full working example.Creating schemas
Dashboard
The dashboard schema editor is the easiest way to create schemas. It provides a visual builder where you select exchanges, configure JSON paths and data types per column, set up unfold rules, and see a live JSON preview. You can switch between the visual editor and the raw JSON view at any time.API
To create a schema programmatically, use the Create export schema endpoint. Here’s a complete trade schema covering Binance and Bybit Linear with unfold:timestamp_ns, price, quantity, side — normalized across both exchanges.
Versioning
Custom schemas are versioned. Every schema starts at version1 when created, and each schema response carries version (the latest published version number) and has_draft (whether an unpublished draft exists).
You can change a schema two ways:
Atomic update — replace the content in one call. Increments version by 1 and discards any draft.
version by 1 and clears has_draft.
Existing exports that referenced earlier versions are unaffected. Each export snapshots the schema content at the time it was created, so a schema change never alters output for already-created exports. Only exports created after publishing use the new version.
404 not_found for built-in IDs.
Using schemas with exports
Once you have a schema (created via dashboard or API), you can reference it three ways when creating an export: By name — for built-in or saved custom schemas:unfold and derive fields as saved schemas — useful when you need one-off batching or array merging without creating a reusable schema.
Next steps
Create Export Schema
Save a reusable schema via the API
Create Export
Use your schema to export data
List Export Schemas
View all available schemas
Quickstart
End-to-end guide from subscription to export