Low-Latency Trading Solutions
CART
API

Crypto market data API — buy over HTTP, stream over WebSocket

The CryptoStruct crypto market data API is self-service: a keyless HTTP flow to find instruments, quote and buy tick-data days (machine-readable as OpenAPI 3.1), a WebSocket feed for live order books and trades, free statistics routes, and an MCP server for coding agents — €1 per instrument-day, no subscription, no account for a guest purchase.

Facts that do not change per call: one file = one instrument × one UTC day, €1 per instrument-day (prices are tax-inclusive EUR); day bundles (kind: series_day) are priced per series, mostly €1 per bundle-day; no account, subscription or API key is needed — a guest checkout only needs an email for the receipt. Card payment ALWAYS happens in the user's browser on a Stripe page: an agent never collects card details. Every endpoint is rate-limited per IP; on a 429 wait for the Retry-After seconds. Machine-readable description: OpenAPI 3.1 · site map for agents: llms.txt · terms · data license.

Find the instrument

GET https://cryptostruct.com/api/search?q=<text>&venue=<code|family>&class=<class>&base=<asset>&limit=25

  • q: ticker (BTCUSDT), pair, base asset, or a numeric instrument id. Venue and class WORDS inside q are understood (binance btcusdt perp, BTCUSDT-PERP, ETH options) and echoed in interpretation.
  • venue: a venue code (binance_swap) or a family (binance = every Binance venue, kraken, okx, gate, htx).
  • class: spot | perpetual | future | call | put | prediction — aliases perp, swap, futures, option (= call + put) are accepted; an unknown value is a 400 with the vocabulary.
  • Response: {query, filters, total, results[{instrument_id, code, type, exchange{code,name}, state, data{days, first_day, last_day, total_bytes}}], interpretation?, did_you_mean?}. Zero hits come with did_you_mean (nearest venue codes, what to drop). Use exchange.code + code + type to confirm you have the right market before buying — BTCUSDT exists on many venues.
  • Free: 100 results per call. The instrument_id is stable and identical across MCP, shop and analytics pages.

Example: curl -s 'https://cryptostruct.com/api/search?q=BTCUSDT&venue=binance&class=perp' → Binance USDT-M perpetual BTCUSDT = instrument_id 67824.

Which days exist

  • GET https://cryptostruct.com/api/instrument/{id}/coverage → {first_day, last_day, days_with_data, coverage_pct, gaps[{from,to,days}], total_bytes} (one call, use it to pick a window).
  • GET https://cryptostruct.com/api/instrument/{id}/days?limit=100&before=YYYY-MM-DD → {days[{date, size}], next_cursor}, newest first — "last week" = the first 7 rows. size is the compressed (zstd) file size in bytes.
  • GET https://cryptostruct.com/api/instrument/{id}/calendar?month=YYYY-MM → the days of one month.
  • Day bundles (prediction series, option chains, futures curves): GET https://cryptostruct.com/api/bundles lists every sellable series with bundle_id, series_key, price_cents, coverage; GET https://cryptostruct.com/api/bundle/{series_key}/days?limit=&before= lists its days. In quote and checkout a bundle day is {instrument_id: <bundle_id>, date, kind: "series_day"}.
  • Unknown and hidden ids answer 404 {error:{code:"NOT_FOUND"}} alike.

Days are UTC; the newest sellable day is normally yesterday (UTC). Only days that exist can be bought — the quote tells you which ones do not.

Quote

POST https://cryptostruct.com/api/quote with {"items":[{"instrument_id":67824,"date":"2026-09-30","kind":"tick"}, …]} (1–365 items, ≤ 20 distinct instruments/bundles, kind defaults to tick; no other keys).

Response: {items_requested, items_available, total_cents, total_eur, currency:"EUR", price_eur_per_day, available[{instrument_id,date,kind}], missing[{instrument_id,date,kind,reason}], bundles?, bundle_days?, vat_note, terms_url, checkout{method,url,headers,body_example,then[]}}. available is the checkout-ready item list; checkout is the next step spelled out. Keyless, 30 calls per minute per IP.

Checkout (guest)

POST https://cryptostruct.com/v1/checkout/session

  • Headers: Content-Type: application/json, Idempotency-Key: <uuid> (reuse the same key when retrying the same basket — never for a new one), X-CryptoStruct-Client: http (marks the order as an HTTP-agent order in our statistics; optional, never affects price or delivery).
  • Body: {"email": "<buyer email>", "items": [<the available[] items from the quote>], "success_url": "https://cryptostruct.com/order/thanks?session_id={CHECKOUT_SESSION_ID}", "cancel_url": "https://cryptostruct.com/shop"} — return URLs must be on https://cryptostruct.com (anything else is a 400).
  • Response 201: {order_id, checkout_url, session_id, currency, item_count, subtotal_cents}. Open checkout_url for the user (show it, don't fetch it): that is the Stripe page where they pay by card. The order is pending until then.
  • Errors: 400 INVALID_INPUT, 422 FILE_NOT_AVAILABLE (details.missing lists the items — drop them and retry), 429 (checkout is limited per IP), 409 ALREADY_OWNED (signed-in only).
  • Signed-in buyers (Clerk session / OAuth bearer) get credits and owned-file dedupe automatically; that path is the MCP server's job — plain HTTP agents use the guest flow above.

Poll until paid

GET https://cryptostruct.com/v1/checkout/session/{session_id} → {status:"pending"} while the user is on the Stripe page, then {status:"paid", order_id, download_token, invoice_hosted_url, invoice_pdf_url}. Poll every 10–15 seconds; a session the user abandons stays pending and simply expires — create a new checkout for a new attempt. 404 = unknown session.

The order

GET https://cryptostruct.com/v1/orders/{order_id} with Authorization: Bearer <download_token> → {order_id, status, paid_at, currency, total_cents, item_count, items[{instrument_id, code, exchange_code, type, date, kind, bytes, price_cents}], invoice_hosted_url, invoice_pdf_url}. A wrong token and an unknown order both answer 404 (no enumeration). After 90 days a guest order answers 410 ORDER_EXPIRED — download within that window, or buy with a signed-in account (never expires). The download_token is the only credential: treat it like a password, keep it out of logs and prompts.

Download

Per tick file: GET https://cryptostruct.com/api/download/order/{order_id}/{instrument_id}/{date}?t=<download_token> streams the .txt.zst file (zstd-compressed JSON lines; line 1 is the instrument master data; zstd -d to inflate). Files are 0.3–2 GB per day for the busiest markets — stream to disk, support Range resumes (curl -C -). A 200/206 carries Content-Length, Accept-Ranges and, once our delivery server has hashed the file, Repr-Digest: sha-256=:<base64>: for verification. A 202 with a JSON body is NOT the file: {state:"pending", reason:"queued"|"downloading"|"restoring"|"archived", eta_seconds} plus Retry-After — poll &status=1 (same JSON, plus sha256 when known) and retry; reason:"archived" means deep archive (days older than ~1 month) — &restore=1 starts the restore (~12 h), then poll &status=1. Instead of ?t=, account orders accept Authorization: Bearer <Clerk JWT or account download key>. Faster path for clients that follow redirects: send X-CryptoStruct-Direct: 1 and a single file may answer 302 to a short-lived storage URL (≤ 1 h, same bytes, Range works, no Repr-Digest) — follow it without the Authorization header (storage rejects it, curl -L drops it) and request this URL again to resume after it expired. Derived flat exports of the same day: &format=trades.csv.gz | bbo.csv.gz | liquidations.csv.gz | trades.parquet | liquidations.parquet — exports are prepared server-side first: the first request answers 202 + Retry-After ({state:"pending", reason:"converting"}) while the file is built (minutes for a flagship day), later requests serve the finished file with Content-Length and Range support; &format=…&probe=1 reports ready | pending | empty | failed without bytes.

Day bundles (kind: series_day): the same route with &kind=series_day plus &zip=1 (every contract file of that day as ONE archive — never one request per contract, a day can hold thousands), &manifest=1 (member list), &status=1 (aggregated {ready,pending,cold}), &restore=1, &zip=1&probe=1 (readiness; poll it until state:"ready" — the probe copies the day's files onto our server, and zip=1 answers 202 pending until every file is local; {state:"too_large"} for days over 4 GB), &complete=1 (409 instead of a partial archive). Whole histories: GET https://cryptostruct.com/api/download/order/{order_id}/{bundle_id}/range?kind=series_day&from=YYYY-MM-DD&to=YYYY-MM-DD&t=<token> streams every owned day of the window as one resumable tar (≤ 100 days per stream; &status=1 / &restore=1 / &manifest=1 companions; a 503 with Retry-After means wait and retry — WARMING while the first day is still being copied to our server, RANGE_BUSY while two streams already run). The full delivery contract is on llms.txt.

Download manifest, tool and account key

Start every bulk pull from the manifest: GET https://cryptostruct.com/api/download/order/{order_id}/manifest?t=<download_token> lists everything the order entitles you to — items[] (tick files with url, status_url, restore_url, bytes, filename, sha256 when known and formats{}) and bundles[] (per day zip_url / status_url / restore_url / manifest_url, tar_url over 4 GB, and range_chunks[] pre-split to the 100-day / 250 GiB caps) — as JSON (schema cryptostruct.download-manifest/1). &as=urls renders it as one URL per line (wget -i, aria2c -i), &as=sh as a probe-then-stream download.sh that resumes and skips what you already have, &as=aria2 as an aria2 input file. Every item carries downloaded_at (last delivery, null = never); the text renderings are new-only by default (&new=0 includes files downloaded before) so a re-run never re-pulls an order by accident. Never guess file URLs — only what the manifest lists exists.

The official tool https://cryptostruct.com/download/cryptostruct-dl.py (one Python ≥ 3.9 file, standard library only) implements the whole contract: python3 cryptostruct-dl.py --order <order_id> --token <download_token> --out <dir> — --dry-run prints the plan, --restore wakes cold files, --verify checks size + Repr-Digest, --include-downloaded re-fetches what was delivered before; exit 0 = complete, 1 = incomplete, 2 = expired link / usage, 3 = interrupted. Signed-in accounts get a download key (cs_dl_…, shown under Get your files on /account/files): Authorization: Bearer <key> on GET https://cryptostruct.com/api/download/manifest lists every paid order of the account, and the same header works on every download URL and on GET /v1/me/orders. The key is download-only — it can never buy, touch credits or realtime — and rotating it in the account stops the old key at once.

  • GET https://cryptostruct.com/v1/me/download-key (Clerk JWT) → {api_key, created_at, rotated_at} — minted on first read; POST https://cryptostruct.com/v1/me/download-key/rotate mints a new one (browser session only — an agent token gets 403 SESSION_REQUIRED).
  • GET https://cryptostruct.com/v1/download-key with Authorization: Bearer <key> → {user_id} (introspection; unknown keys answer a uniform 404).
  • POST https://cryptostruct.com/v1/downloads/delivered {order_id, via: "tool", items:[{instrument_id, date, kind}]} (bearer = token, JWT or key) marks items as delivered — our delivery server reports every completed stream this way; a client that fetched through other means may report too, so downloaded_at stays truthful.
  • Every download URL and GET /v1/me/orders accept the key in the Authorization header as well; ?t= stays the guest path.

Try before buying

GET https://cryptostruct.com/api/samples → curated free full-day files in the exact shop format, each with a direct href (/api/download/sample/{id}/{date}, no auth, ?format= supported). Parser, format reference and recipes: the Agent Skill and the downloads page.

Buy link (no API call needed): https://cryptostruct.com/shop?i=<instrument_id>&d=<from>..<to> opens the shop with those UTC days of the instrument in the cart; d also takes a comma list or a mix (d=2026-09-01..2026-09-07,2026-09-15). Bundles: https://cryptostruct.com/shop/bundles?bundle=<series_key>&d=…. Only archived days are added (gaps and days still being merged are skipped), at most 366 days of span and 1000 lines per cart; the user pays on the site. Example: https://cryptostruct.com/shop?i=67824&d=2026-09-29..2026-10-05 = seven days of Binance BTCUSDT perpetual ticks. No verified instrument_id? Never guess one — name the code instead: https://cryptostruct.com/shop?venue=<venue_code>&code=<instrument_code>&d=… (venue=binance_swap&code=BTCUSDT) resolves an exact code that is unique on that venue to the id link; anything else opens the shop search. The page lists what landed in the cart.

Free endpoints — no key, no purchase

  • Instrument search: GET https://cryptostruct.com/api/search?q=<text> (section 1) — 100 results per call.
  • Coverage and days: GET https://cryptostruct.com/api/instrument/{id}/coverage, …/days, …/calendar?month=YYYY-MM (section 2) — also the sellable day bundles via GET https://cryptostruct.com/api/bundles.
  • 1-minute statistics: GET https://cryptostruct.com/api/analyze/instrument-last-24h-minutes/{id}?format=csv — the rolling last 24 h of any instrument as a 22-column CSV (OHLC, VWAP, trades, turnover, spreads, depth), computed from every recorded trade; the free CSV carries a # source line above the header.
  • Free sample days: GET https://cryptostruct.com/api/samples (section 8) — complete UTC days in the shop format, with the CSV/Parquet exports.
  • Quote: POST https://cryptostruct.com/api/quote (section 3) — the EUR total and the checkout recipe, keyless.

Instrument ids and master data

Every endpoint keys on the numeric instrument_id — stable, identical across the shop, the MCP server, the analytics pages and the realtime feed. Resolve it with the search API (https://cryptostruct.com/api/search?q=BTCUSDT&venue=binance&class=perp → 67824 for the Binance USDT-M perpetual) and confirm exchange.code + code + type, because the same ticker exists on many venues. Line 1 of every tick file repeats the instrument master data (tick size, lot size, contract specs), so a downloaded day is self-describing; realtime buyers additionally export the master data of every market they hold from the instrument directory (next section).

WebSocket real-time API

Live data is a WebSocket API bought by the UTC day: Binance USDT-M, Binance Spot, Coinbase Spot, Kalshi, Polymarket and OKX stream over WebSocket in JSON or SBE, captured at each venue and delivered at our London, Frankfurt, Ashburn, Ohio or Tokyo endpoints over dedicated lines (Binance and OKX from Tokyo at London, Frankfurt, Ashburn or Ohio; Coinbase from Ashburn at Ohio or Tokyo; Kalshi from Ohio at Tokyo; Polymarket from London at Tokyo) — €49 per instrument-day or €99 per market-day, one API key per account, nothing renews. The port selects the market: Binance USDT-M (port 14004), Binance Spot (port 14003), Coinbase Spot (port 14006), Kalshi (port 14005), Polymarket (port 14007) and OKX (port 14008). The key is created with the first realtime purchase and shown in the account; it authenticates the feed (?apiKey= on the WebSocket URL) and the instrument directory below.

  • Real-time crypto market data: every venue, its endpoints and lines with their one-way latency, feed contents per market, prices.
  • Connection guide: login, subscribe flow, every message type, keep-alive and reconnect rules, runnable Python and Node examples.
  • Feed spec as markdown: the connection guide as one document for coding agents — the API key is read from the environment, never pasted into prompts.
  • Instrument directory: GET https://cryptostruct.com/api/realtime/instruments?market=<code> with the realtime key as Authorization: Bearer (add &format=csv; poll &since=<as_of> every five minutes for new instruments and state changes) — lists the markets the key holds a day for.

The same flow as MCP tools

Every step above is also an MCP tool: the remote MCP server exposes search, coverage, live statistics, quotes, samples and checkout to any MCP client (keyless free tier; sign-in adds credits, owned-file dedupe and your orders). The MCP docs as markdown and the server card describe it for agents.

Limits and conduct

  • Rate limits per IP: /api/search 120/min (named search and AI crawlers 240/min); coverage and the other reads share the general API bucket; /api/quote 30/min; checkout 10/min; order reads 30/min. Back off on 429 and honor Retry-After.
  • Never ask the user for card details; never paste a download_token into a chat transcript; keep the Stripe URL for the user.
  • Third-party exchange APIs are not proxied here; for a venue's own endpoints and limits, check the current docs of that venue.
Why CryptoStruct

Why buy the API here

Four things every page on this site is built on — and the reason the numbers above exist at all.

We record everything

The complete public feed of each venue as it was published: every Level-2 snapshot and update at the venue's full book depth, every trade with its aggressor side, every quote, funding, mark-price and liquidation event — for every instrument the venue lists, every UTC day since we added the venue. Nothing sampled, no top-N cut, no on-demand capture.

Institutional grade

Captured co-located at the venue with the exchange timestamp and our receive timestamp in integer nanoseconds, an event-id chain that makes any gap visible, and one normalized schema across 35+ venues — the same capture our own high-frequency trading engine and enterprise feeds run on.

€1 per instrument-day

Any instrument-day is €1, series-day bundles start at €1 — no subscription, no minimum order, no tiers to unlock. Credit packs lower the effective price and never expire, and every venue has free full-day samples to test against first.

Self-service for everyone

Pick the days in the Data Shop, pay by card as a guest and download immediately — no sales call, no enterprise contract, no KYC. Coding agents buy the same files through the MCP server, and the free Agent Skill teaches them the format.

Agents: /llms.txt · MCP server /mcp · every page as markdown via Accept: text/markdown

FAQ

Questions about the crypto market data API

Do I need an API key to use the crypto market data API?

Not for the historical flow: search, coverage, quote and the guest checkout are keyless, and the paid order is read and downloaded with the download_token the checkout session returns — no account, no sign-up. Signed-in accounts can additionally use a download-only key (cs_dl_…, under Get your files on the account page) to list and pull every order with one credential. The WebSocket real-time feed has its own key, created with your first realtime purchase.

Is there an OpenAPI specification for the market data API?

Yes — https://cryptostruct.com/openapi.json describes every documented endpoint in OpenAPI 3.1 (also at /.well-known/openapi.json, and linked as service-desc from /.well-known/api-catalog). The walkthrough on this page is the human-readable twin; the markdown rendition at /docs/api.md is the one to hand to a coding agent.

Can I get real-time crypto market data through the API?

Yes — as a WebSocket API bought by the UTC day: Binance USDT-M, Binance Spot, Coinbase Spot, Kalshi, Polymarket and OKX in JSON or SBE, captured at each venue and delivered at our London, Frankfurt, Ashburn, Ohio or Tokyo endpoints over dedicated lines (Binance and OKX from Tokyo at London, Frankfurt, Ashburn or Ohio; Coinbase from Ashburn at Ohio or Tokyo; Kalshi from Ohio at Tokyo; Polymarket from London at Tokyo), €49 per instrument-day or €99 per market-day with one API key, nothing renews. The endpoints, lines and feed contents are on real-time crypto market data, the protocol in the connection guide.

Which endpoints are free to call?

Instrument search, coverage, the day lists and calendars, the sellable-bundle list, the free sample days and the rolling 24-hour 1-minute statistics CSV of any instrument — all keyless, rate-limited per IP. Only the purchased tick days and their CSV/Parquet exports need the order's download_token.

How do I find the instrument id for a symbol?

Call /api/search with the ticker and, ideally, the venue and class (q=BTCUSDT&venue=binance&class=perp): the response carries the numeric instrument_id plus exchange.code, code and type to confirm the market, because the same ticker trades on many venues. The id is stable and identical across the shop, the MCP server and the analytics pages.

What does buying through the API cost?

The same as in the shop: €1 per instrument-day and series-day bundles priced per series, tax-inclusive EUR — no API surcharge, no subscription, no minimum order, no account. The quote returns the exact total before any checkout, and the card payment happens on a Stripe page the user opens.