Documentation

API reference

Everything for the DepthFeed v3 API — authentication, every endpoint, the response contract, the data model, and the venues behind it. Base URL https://api.depthfeed.com/v3.

Contents

Introduction

DepthFeed is historical order-book depth for prediction markets — recorded full bid/ask ladders on both sides for Polymarket, Kalshi, and Limitless. Not just the last trade price or top of book: each stored observation includes resting levels and sizes. It is the data you need to backtest a fill against the liquidity that was actually there.

It is delivered over a metered REST API — market discovery, metadata, and latest + historical order-book snapshots, keyset-paginated with separate general-admission and historical-workload controls. Pull exactly the depth your strategy needs and replay it in your own stack.

Order-book coverage spans 3 venues, 7 assets (btc · eth · sol · xrp · doge · bnb · hype) and the recurring up/down crypto markets each venue runs (5m, 15m, 1h, 4h, 24h, depending on venue).

Prices are the second half of the product. /v3/bars serves OHLCV price bars for US-listed equities and ETFs, crypto spot pairs and perpetual futureson every plan, at one-minute base resolution. They share the clock, the schema and the plan history window with the books above, so a strategy can read an equity and act on a contract without reconciling two vendors’ timestamps.

Sports is on every plan — the whole REST surface: Polymarket and Kalshi books, sportsbook odds, scores and injuries across the major leagues. What a paid plan adds is the low-latency live book over WebSocket, one concurrent book on Quant and three on Desk. Jump to the Sports endpoints.

New to evaluating data sources? The buyer’s guide covers the five criteria that decide whether prediction-market data is actually backtestable, and how DepthFeed clears each.

Independent project. Not affiliated with Polymarket, Kalshi, Binance, Chainlink, or Limitless. Our own capture runs live, and licensed historical depth makes the longest-running Polymarket series available from January 2026. Exact per-series archive floors are listed under History & coverage. Every plan window is backed by stored data. Third-party data attribution is listed in the terms.

Quickstart

1. Create a free account and mint an API key from the dashboard. Keys look like df_… and are shown once.

2. Make your first authenticated call:

shell
curl https://api.depthfeed.com/v3/whoami \
  -H "Authorization: Bearer df_your_key_here"

3. The response is a JSON envelope — your plan, limits, and history window:

json
{
  "data": {
    "user_id": "acct_…", "plan": "pro",
    "rps": 25, "rpm": 1000, "history_days": 30, "coins": "all", "sports": false,
    "bars": { "max_symbols_per_request": 8 }
  },
  "meta": { "request_id": "req_…", "timestamp": "2026-06-07T02:27:09.532Z" }
}

4. Pull the latest BTC up/down markets, then the order-book depth for one of them — see Markets and Snapshots.

Authentication

Every endpoint except /v3/health and /v3/overview requires an API key. Pass it either way — both schemes are accepted:

shell
# Authorization header (recommended)
curl https://api.depthfeed.com/v3/btc/markets -H "Authorization: Bearer df_your_key"

# or the X-API-Key header
curl https://api.depthfeed.com/v3/btc/markets -H "X-API-Key: df_your_key"
/v3/streamalso requires a key, but authenticates once at connection time — a WebSocket can’t carry a per-request header, so it accepts X-API-Key, Authorization: Bearer, or a ?api_key= query param. See Live stream.

Keys are minted per account from the dashboard and carry a plan that sets your rate limit, history window, and asset/venue access. Keys are stored SHA-256-hashed at rest — the raw value is shown exactly once at creation. Revoking a key in the dashboard takes effect immediately. Quotas are per user, not per key — extra keys do not multiply your limit.

FailureStatuscode
No key supplied401AUTH_MISSING
Unknown / revoked / malformed key401AUTH_INVALID
Key store temporarily unreachable503AUTH_UNAVAILABLE

Base URL & versioning

All endpoints live under a single versioned base path:

text
https://api.depthfeed.com/v3

TLS is required (Let's Encrypt, HTTP/2). The current API version is v3, built to PolyBackTest parity (same envelope, cursor pagination, and error-code enum) over our superset data (3 venues, 7 assets). Legacy v1/v2 namespaces are not served.

Response format

Every successful response is the same envelope:

json
{
  "data": … ,                 // object (single) or array (list)
  "pagination": { … },        // present only on list endpoints
  "meta": { "request_id": "req_…", "timestamp": "2026-06-07T02:27:09.532Z" }
}

Errors share the shape, with the HTTP status set per code:

json
{
  "error": { "code": "INVALID_COIN", "message": "…", "details": { … } },
  "meta": { "request_id": "req_…", "timestamp": "…" }
}

Every response — success or error — carries meta.request_id, also returned as the X-Request-Id header. Quote it in support requests.

Errors

Branch on error.code — it is stable. The HTTP status is implied by the code.

codeHTTPMeaning
AUTH_MISSING401No API key on the request.
AUTH_INVALID401Key is unknown, revoked, or malformed.
COIN_NOT_IN_PLAN402Returned only by the anonymous demo surface (BTC sample). Every account plan, Explorer included, includes every coin.
ENDPOINT_NOT_IN_PLAN402Returned only by the anonymous demo surface. Every account plan includes every venue; history is bounded by your plan window.
SYMBOL_LIMIT_EXCEEDED402More symbols in one /v3/bars request than your plan's batch size (3 / 8 / 16 / 32). Split the list and re-request — this bounds ONE call, not how many symbols you may pull.
WINDOW_TOO_WIDE400One /v3/bars request spans more time than the frame's row count allows (45 days at 1Min, 180 at 5Min, a year at 15Min and 1Hour, ten years at 1Day). Page with limit and cursor.
HISTORY_LIMIT_EXCEEDED403start_time / timestamp is older than your plan window.
INVALID_COIN400Coin not one of the seven supported.
INVALID_CURSOR400Cursor is malformed.
INVALID_TIMESTAMP400Not ms-epoch or ISO-8601.
INVALID_INTERVAL400interval not a duration like 30s, 1m, 5m, 1h, 1d (1s–1d).
INVALID_PARAMETER400A query parameter or URL variable was rejected; details.parameter names it. Covers fill sent without interval, and an empty {ticker}/{slug}/{asset_id}/{market_id} segment — a path with two consecutive slashes, which almost always means a template variable did not get substituted.
MISSING_PARAMETER400A required parameter was absent; details.parameter names it.
INVALID_LIMIT400limit is not a positive integer.
INVALID_JSON400The request body is not valid JSON. Only POST routes can return it.
INVALID_RANGE400On /v3/bars: end_time is not after start_time. Carries both values back.
INVALID_SYMBOL400On /v3/bars: the symbol is not one we serve. Spell a pair the way the venue does — BTCUSDT, not BTC.
INVALID_TIMEFRAME400On /v3/bars: not one of 1Min, 5Min, 15Min, 1Hour, 1Day. details.supported lists them.
INVALID_VENUE400On /v3/bars: venue is not us-equities, binance, or binance-futures.
INVALID_WINDOW400On /v3/screener/{asset}/{window}: the window is not one of 5m, 15m, 1h, 4h, 24h.
INVALID_METRIC400On the Binance-prediction timeseries: metric is not chance, price, or last_trade.
INVALID_MARKET_ID400On /v3/binance-prediction/{market_id}/*: the id is not an unsigned integer.
BAD_REQUEST400The bar service rejected the request and its own message is passed through. Read error.message.
MARKET_TICKER_REQUIRED400A Kalshi series was passed where an exact discovered market ticker is required.
BATCH_TOO_LARGE400More than 10 sub-requests in a batch.
BATCH_INVALID_PATH400Sub-request is not a GET /v3/ path (per-sub status).
TICKS_NOT_IN_PLAN402Tick-level routes are Desk-only.
MARKET_NOT_FOUND404No market with that id / slug / ticker.
SNAPSHOT_NOT_FOUND404No book has been captured for the query — the market may not have opened for trading yet, may have expired, or may be outside coverage.
CANDLES_NOT_AVAILABLE404No candle series is recorded for that Kalshi ticker.
GAME_NOT_FOUND404No game with that game_slug.
ENDPOINT_NOT_FOUND404Path matches no v3 route.
SPORTS_UNKNOWN_ROUTE404Path is under /v3/sports but matches no sports route. GET /v3/sports lists every one.
SPORTS_METHOD_NOT_ALLOWED405Wrong HTTP verb for a sports route.
RESPONSE_TOO_LARGE413The page exceeds the response ceiling. Lower limit, narrow the window, or add interval.
RATE_LIMIT_BURST429Per-second burst exceeded.
RATE_LIMIT_SUSTAINED429Per-minute sustained limit exceeded.
HISTORY_CONCURRENCY_LIMIT429Another snapshot or batch history request is still running for this account (details.scope = "account").
HISTORY_POOL_BUSY429The shared archive-query pool is saturated server-side, not your account's limit (details.scope = "shared_pool") — retry with backoff.
SERVICE_PROFILE_UNAVAILABLE503Your account’s serving configuration could not be read. Transient; details.retry_after is in seconds.
HISTORY_METER_UNAVAILABLE503Historical usage accounting is down, so a history read cannot be admitted. Transient; carries details.retry_after.
HISTORY_FALLBACK_UNAVAILABLE503A range missing from the live store could not be fetched from the archive right now. Transient; carries details.retry_after.
BARS_LICENCE502Price history is not licensed for that range. Narrow the window rather than retrying.
HISTORY_START_RATE_LIMIT429This account has started as many historical queries as its profile allows this minute. Retry-After: 60.
HISTORY_DELIVERY_LIMIT429The account's rolling historical-delivery allowance is exhausted. Retry-After: 3600.
TRIAL_DELIVERY_LIMIT429The data allowance included with the free trial is used up. Not a fault, and not fixed by waiting — subscribe for the full plan allowance.
INTERNAL_ERROR500Unexpected server error — retry, then contact us with the request id.
AUTH_UNAVAILABLE503Key verification is temporarily unavailable — retry shortly.
UPSTREAM_UNAVAILABLE503A depth service behind the API did not answer (details.retryable = true). This is about us, not your ticker — retry with backoff rather than treating the market as missing.
BARS_BUSY503The bar service is at its concurrency ceiling right now, not an outage. Carries details.retry_after in seconds — back off and retry, the same request usually succeeds.
BARS_UNAVAILABLE503The bar service is down. Retry with backoff.
BARS_UPSTREAM502The bar provider answered with an error.
SPORTS_UNAVAILABLE503The sports gateway did not answer. About us, not your market.
SPORTS_UPSTREAM502The sports gateway answered with an error.
ISOLATED_ROUTE_UNAVAILABLE503Desk only: the dedicated Desk history node is not reachable. Retry with backoff.
ISOLATED_ROUTE_FAILED502Desk only: the dedicated Desk history node answered with an error.

403 HISTORY_LIMIT_EXCEEDED carries details.max_history_days and details.oldest_allowed. 429 also sets Retry-After and the X-RateLimit-* headers.

Rate limits

Limits are a per-user token bucket with two windows: a per-second burst and a per-minute sustained cap. Exceeding either returns 429 with the matching code and a Retry-After of 1 second.

Explorer is one request per second, with no separate per-minute cap. It is a pace, not a burst allowance: a loop that fires as fast as it can will spend most of its requests on 429. Read X-RateLimit-Remaining, or simply sleep a second between calls.

The advertised rps/rpm values are general REST admission limits, not archive-delivery rates. Historical snapshots, batches containing history reads, and other heavyweight routes use separate account-level concurrency and delivery controls. These controls apply independently of the general token bucket and do not change the plan's history window.

Explorer, Quant, and Research currently accept one historical request in flight per account. Desk accepts 2 parallel historical snapshot requests per account through its dedicated Desk query pool. An overlap returns 429 HISTORY_CONCURRENCY_LIMIT with Retry-After: 2; retry only after an active request completes. General rps/rpm values must not be used to infer historical parallelism.

A historical read can also be rejected when your account is still inside its own allowance, because the shared archive-query pool is saturated. That case returns 429 HISTORY_POOL_BUSY with details.scope = "shared_pool" instead of "account". Branch on details.scope: on account, lower your parallelism; on shared_pool, lowering parallelism will not help — retry with backoff.

/v3/bars has its own concurrency ceiling, independent of both controls above. Past it a request is refused immediately with 503 BARS_BUSY and details.retry_after in seconds, rather than being queued — a fast refusal you can retry is more useful than a slow success you cannot budget for. Treat it exactly like a 429: back off and retry the same request. Bars also carry two limits that are not rate limits at all — batch size (402 SYMBOL_LIMIT_EXCEEDED) and per-request span (400 WINDOW_TOO_WIDE) — both fixed by splitting the request, not by waiting.

HeaderMeaning
X-RateLimit-LimitYour per-second burst limit (rps).
X-RateLimit-RemainingBurst tokens left this second.
Retry-AfterSeconds to wait (set on 429).
X-Request-IdEcho of meta.request_id.

See Plans & access for the rps/rpm per tier.

Historical query pattern

Keep no more than your confirmed history concurrency active. Use bounded start_time andend_time, cursor pagination, and limit=1000; omit include_count and request the full ladder only when needed. Add interval when a downsampled series is sufficient. DepthFeed is a metered query-access product: bulk files, offline archive delivery, database replication, and systematic archive mirroring are not included.

Pagination & filters

List endpoints use opaque keyset cursors — O(1) at any depth. Pass ?cursor= from the previous page's pagination.next_cursor; stop when has_more is false.

json
"pagination": {
  "next_cursor": "6274…",   // opaque; feed back as ?cursor=
  "has_more": true,
  "limit": 50,
  "count": 50,
  "total_count": 1284        // only when ?include_count=true
}

Common query parameters

ParamTypeNotes
limitintMarkets max 100, snapshots/trades max 1000. Defaults: 50 / 100.
cursorstringOpaque keyset cursor from the prior page.
include_countboolAdds total_count (costs a COUNT — opt-in).
start_timetsms-epoch or ISO-8601. Older than your window → 403.
end_timetsms-epoch or ISO-8601.
fromtsAlias for start_time, accepted on every route.
totsAlias for end_time, accepted on every route.
intervaldurSnapshots only. Downsample to one book per bucket (latest in each): 30s, 1m, 5m, 1h … up to 1d. Omit for full resolution.
fillenumSnapshots only, and requires interval. ffill returns one row per bucket, carrying the last book into buckets that held no capture. Default off.

from/to and start_time/end_time are the same window. Both spellings work everywhere; if a request somehow carries both, start_time/end_time win.

Timestamps accept either milliseconds-since-epoch (e.g. 1780774485780) or ISO-8601 (e.g. 2026-06-07T02:27:09Z). Every value reaching the database is validated and injection-safe. The plan history window is enforced on every list/snapshot endpoint: an explicit start_time beyond the window returns 403; an omitted one is clamped to the window floor.

Downsampling with interval

By default the snapshot endpoints return every recorded book change — full event-driven resolution. When you only need a periodic sample (say one book per minute for a chart or a coarse backtest), add ?interval= and the server collapses each time bucket to a single snapshot: the most recent book in that bucket. It is far less data over the wire than fetching every tick and thinning client-side.

shell
# one snapshot per minute instead of the raw stored series
curl "https://api.depthfeed.com/v3/btc/markets/{id}/snapshots?interval=1m&include_orderbook=true" \
  -H "Authorization: Bearer df_your_key"

Accepts a duration: a bare number of seconds, or a value with a unit — 30s, 1m, 5m, 15m, 1h, 4h, 1d — from 1s to 1d. Buckets are aligned to the epoch, so 1m snaps to wall-clock minute boundaries. Cursor pagination and include_count apply to the downsampled rows (total_count becomes the number of buckets). Omit it, pass raw, or pass 0 for full resolution. An unrecognised value returns 400 INVALID_INTERVAL. Works on every snapshot endpoint — Polymarket, Kalshi, Limitless, PredictFun, Binance, and sports.

Interval selects; it does not interpolate. A bucket appears in the response only if a book was captured inside it. Two hours at ?interval=1m spans 121 minute boundaries but returns only the minutes that held a capture — commonly ten to thirty on a market that is not actively quoted. That is the full record for the window, not a truncated page.

Filling the gaps with fill=ffill

An order book holds its state until something changes it, so a minute with no capture is not missing data — the book from the previous minute was still the book. ?fill=ffill says so explicitly: one row per bucket for the whole window, with the last captured book carried into the buckets that held no capture. It is what you want when the series has to line up against a fixed clock, a price bar, or another venue.

shell
# a dense 1-minute grid: 121 rows for a 2-hour window, gaps and all
curl "https://api.depthfeed.com/v3/btc/markets/{id}/snapshots?interval=1m&fill=ffill&from=1788202800000&to=1788210000000" \
  -H "Authorization: Bearer df_your_key"

fill requires interval; on its own it returns 400 INVALID_PARAMETER. Accepted values are ffill (also previous, prev, true, 1) and none (also false, 0, the default). It is supported on the same routes as interval. The listing, tick, candle, and orderbook/latest routes have no grid to fill and ignore the parameter rather than reject it, so a client that sends it everywhere still gets a normal 200 from them.

The row’s own clock changes when you turn fill on. With interval alone the row is a real capture, so id and time/timestampare the instant it was recorded — 04:37:59.926 for the 04:37 bucket. With fill=ffill every row stands for a bucket, including the captured ones, so those fields become the bucket boundary (04:37:00.000) and the capture instant moves to as_of_ts. Nothing is lost either way, but a chart drawn from both will be offset by up to one interval unless you read the same field in both. If you need the grid and the capture instant together, ask for fill=ffill and read as_of_ts.

Reading a filled row

Filled responses add three fields, and only filled responses have them. A request without fill returns exactly the shape it always did.

FieldMeaning
filled0 when the row is a captured book, 1 when it was carried forward.
as_of_tsWhen the book in this row was actually recorded. On a carried row this is older than the bucket, and the distance between them is exactly how stale the book is. null when there was no earlier book to carry.
as_of_ts_msThe same instant as ms-epoch.

The bucket timestamp and as_of_ts are close but not equal even on a capture, because the capture landed somewhere inside the bucket rather than on its boundary: 2026-09-02T00:50:00.000Z with as_of_ts of 2026-09-02T00:50:44.437Z is a real minute that held a real book.

The first row of a filled window is usually a carried one. The grid is seeded with the newest book recorded before the window opened, so the series starts from a real state instead of an empty one. Its as_of_ts can be hours older than the first bucket, and that is the honest answer.

When the window opens before the market did, there is no earlier book to carry. Those leading buckets come back with an empty ladder and as_of_ts: null — generated, with nothing to put in them. Bound start_timeto the market’s own start to skip them.

The resolution block

Every response that used ?interval= carries a resolution block, so you can tell a sparse capture record from a truncated page without a second request.

json
"resolution": {
  "interval_secs": 60,
  "buckets_in_range": 121,   // minute boundaries the window spans
  "buckets_filled": 29,      // minutes that held a CAPTURE
  "rows_returned": 121,      // rows on this page
  "fill": "ffill",           // absent when fill is off
  "complete": true           // false while a next_cursor is outstanding
}

buckets_filled counts captures, never rows, so it means the same thing with fill on as with it off. It is the number to compare against buckets_in_range when judging how well a market is quoted. With fill off, rows_returned and buckets_filled agree. With it on, the gap between them is how much of the grid was carried. If buckets_filled is far below buckets_in_range, ask for a coarser interval, or drop interval and read every capture.

Plans & access

Four flat plans. The plan field returned by /v3/whoami is the raw value in the right column.

PlanplanPriceGeneral rpsGeneral rpmHistorical parallelismUnified historyWallet IntelligenceCryptoSports RESTLive sports books
Explorerfree$0117 days1 saved · 3 auditsAll assets + venuesAll sports venuesREST only
Quant pro$29/mo251,000130 days5 saved · 10 auditsAll assets + venuesAll sports venues1
Researchscale$99/mo503,000190 days10 saved · 25 auditsAll assets + venuesAll sports venues2
Deskenterprise$249/mo1006,0002 parallelFull archive20 saved · 50 auditsAll; dedicated Desk historical query poolAll sports venues3

There are no Polymarket or Kalshi add-ons. Both sports venues use the same key, schema, rate limit, and history window; upgrading buys more history, throughput, and live-book concurrency.

What Explorer reaches

Explorer is a real key on real data, not a sandbox: all seven coins, every venue, seven days of history, the whole sports REST surface, price bars, and the screener. A venue is never something a plan buys. What an upgrade buys is the history window, throughput, stream subscriptions, and tick data. Every key returns the full ladder; the only trimmed book on the API is the keyless demo, at eight levels a side.

The one route family Explorer cannot reach is ticks, which are Desk-only on every venue and return 402 TICKS_NOT_IN_PLAN below that. The anonymous demo surface — no key at all — is separately limited to a BTC sample, and it is the only thing that answers 402 COIN_NOT_IN_PLAN.

The General rps/rpm columns describe request admission, not simultaneous archive scans. Historical query parallelism and delivery controls are separate. Desk historical reads use a dedicated Desk-only query pool isolated from regular-plan query traffic, without removing account-level controls. The pool is shared across Desk accounts and is not customer-owned or account-exclusive infrastructure.

Gating is enforced server-side:

  • Markets — every plan, Explorer included, reaches every coin, every venue, and the full order book; what tiers is throughput, history and ticks, not access.
  • History — a window outside your plan → 403 HISTORY_LIMIT_EXCEEDED; upgrading widens the window, never the market list.
  • Sports — the /v3/sports/* REST surface is included on every plan; the same plan history window applies to both venues.
  • Historystart_time beyond your window → 403 HISTORY_LIMIT_EXCEEDED.
  • Price bars /v3/bars obeys the same history window as depth, and additionally caps symbols per request (3 / 8 / 16 / 32), over which it returns 402 SYMBOL_LIMIT_EXCEEDED.
  • Streaming — limits apply at subscribe time on /v3/stream; every plan streams every venue — Explorer is capped at one concurrent subscription, paid tiers raise the count.

Health & identity

GET/v3/healthno auth

Liveness probe, no auth. Returns { status: "ok", service, time }.

GET/v3/whoami

Your plan, rate limits, unified history window, coin access, and sports access — the fastest way to confirm a key works.

Markets

Polymarket up/down markets per coin, joined to settlement reference (open/close price, winner, volume). {coin} is one of btc · eth · sol · xrp · doge · bnb · hype.

GET/v3/{coin}/markets

Filters: type (5m/15m/1h/4h/24h), resolved (true/false), plus the common time + pagination params.

GET/v3/{coin}/markets/{market_id}
GET/v3/{coin}/markets/by-slug/{slug}

A single market by id or slug. Example response (per-coin price keys are dynamic, like the reference):

json
{ "data": {
  "market_id": "2456720", "event_id": "566750",
  "slug": "btc-updown-5m-1780860300", "market_type": "5m",
  "start_time": "…", "end_time": "2026-06-07T19:30:00Z",
  "condition_id": "0xb7ce…",
  "clob_token_up": "15598…", "clob_token_down": "51823…",
  "btc_price_start": null, "btc_price_end": null,
  "winner": null, "final_volume": null, "final_liquidity": null, "resolved_at": null
}, "meta": { … } }
The settlement fields — *_price_*, winner, resolved_at, final_volume, and final_liquidity — are null until the market settles and the reference backfills (~12–24 h after settlement).
GET/v3/polymarket/markets

The whole Polymarket venue, not just the crypto up/down markets. The routes above address markets by coin, so they can only reach markets that have one — elections, geopolitics, equities and FX have no coin and no URL there. This lists everything.

Filters: ?q= (slug search), ?type=eventfor everything that isn't an up/down ladder, plus ?coin=, ?event= and ?outcome=. Markets here are keyed by CLOB asset_id (token id); pass that to /v3/polymarket/{asset_id}/snapshots for depth history or /v3/polymarket/{asset_id}/ticks for the tape. Non-crypto markets carry an empty base_asset— they have no coin, and we don't invent one.

GET/v3/polymarket/{asset_id}/snapshots

Order-book snapshot history for any Polymarket market — sports, politics, news and crypto alike — bounded by your plan's history window. Same shape and pagination as the per-coin snapshot routes.

GET/v3/polymarket/{asset_id}/ticksenterprise

The venue-wide tape for the same asset_id keys.

Order-book snapshots

The order-book depth time-series for a market, with the underlying coin price stamped on each snapshot.

GET/v3/{coin}/markets/{market_id}/snapshots

Add ?include_orderbook=true to get the full ladder (omitted by default for a lighter payload). Time-windowed + cursor-paginated. Add ?interval=1m (or 5m, 1h, …) to downsample to one book per bucket instead of the raw stored series — see Pagination & filters. The list response is an array with a pagination block:

json
{ "data": [{
  "id": "1780774485780", "time": "2026-06-06 19:34:45.780",
  "market_id": "2456720", "btc_price": 60606.68,
  "price_up": 0.505, "price_down": 0.495,
  "orderbook_up":   { "bids": [[0.50, 304.25], …], "asks": [[0.51, 30.0], …] },
  "orderbook_down": { "bids": [ … ],               "asks": [[0.50, 304.25], …] }
}], "pagination": { … }, "meta": { … } }
GET/v3/{coin}/markets/{market_id}/snapshots/at/{timestamp}

The single snapshot closest to a timestamp (within ±2 s). {timestamp} is ms-epoch or ISO-8601. Unlike the list endpoint, data is a single object (the same row shape) with no pagination block; no match within ±2 s returns 404 SNAPSHOT_NOT_FOUND.

json
{ "data": {
  "id": "1780774485780", "time": "2026-06-06 19:34:45.780",
  "market_id": "2456720", "btc_price": 60606.68,
  "price_up": 0.505, "price_down": 0.495,
  "orderbook_up":   { "bids": [[0.50, 304.25], …], "asks": [[0.51, 30.0], …] },
  "orderbook_down": { "bids": [ … ],               "asks": [[0.50, 304.25], …] }
}, "meta": { … } }

Levels are [price, size]. price_up is the best-bid/ask midpoint; price_down = 1 − price_up. The orderbook_down side is derived as the binary complement of the up book (down = 1 − up, sizes preserved). The coin price is the nearest preceding tick via an ASOF join — exact for 1h/4h/24h, a labeled Binance proxy for 5m/15m.

Every row is a complete book as of its timestamp, never a delta against the row before it, so you never have to rebuild state from changes. For the change stream itself, use ticks.

The other venue snapshot routes

The same contract — window, cursor, interval, fill, plan history window — is served by one route per venue. They differ in the identifier they take and in how a binary market’s two sides are presented, because the venues themselves do.

RouteIdentifierBook shape
/v3/{coin}/markets/{market_id}/snapshotsNumeric market_id from /v3/{coin}/marketsThe per-coin event view: price_up/price_down only, unless you add include_orderbook=true
/v3/polymarket/{asset_id}/snapshotsToken id — clob_token_up/clob_token_down, or asset_id from /v3/polymarket/marketsOne token’s book: bids and asks arrays, always included
/v3/kalshi/{ticker}/snapshotsticker from /v3/kalshi/marketsyes and no ladders
/v3/limitless/{slug}/snapshotsslug from /v3/limitless/marketsbids, asks, and a midpoint
/v3/predictfun/{id}/snapshotsmarket_id from /v3/predictfun/marketsNested up and down objects, each with bids/asks
/v3/{coin}/{venue}/snapshotsvenue is spot or futures — not an exchange name20-level underlying book: bids, asks, price

The Polymarket route reaches the whole venue, not only the crypto ladders — election, sports, and everything else Polymarket lists. The per-coin route is the crypto up/down view with the underlying price joined on.

Tick data — every book change

The raw tape: every individual order-book change, not a sampled snapshot series. Where snapshots give you the book at points in time, ticks give you every change between them — what a market-making or execution-research workflow needs to ask where the book was the instant before a fill, or how long a level rested before it was taken.

Enterprise only. Other plans receive 402 TICKS_NOT_IN_PLAN.

GET/v3/{coin}/markets/{market_id}/ticksenterprise

Polymarket, sourced from the CLOB delta stream. Both outcome tokens are returned by default and tagged outcome; narrow with ?outcome=up or ?outcome=down.

History: the tape is continuous from 2026-06-25(checked 2026-09-02) and keeps accumulating, so the dependable window grows by a day every day. Days before that exist in the archive but are not contiguous — 61 of them are missing between 2026-04-14 and the continuous run — so a backtest that needs an unbroken series should start no earlier than 2026-06-25.

Coverage: the up/down markets on the 5m, 15m and 24h windows, across all seven assets. The 1h and 4h up/down families are not currently in the tick tape — use snapshots for those.

json
{ "data": [{
  "id": "1780774485780", "time": "2026-06-06 19:34:45.780",
  "received_ms": "1780774485812", "outcome": "up",
  "token_id": "7194…", "side": "BUY", "price": 0.505, "size": 1240.0
}], "pagination": { … }, "meta": { … } }
GET/v3/kalshi/{ticker}/ticksenterprise

New — Kalshi tick capture began 2026-08-07. There is no Kalshi tick history before that date; the tape accumulates forward from it. Kalshi snapshots remain available over the full history window.

Coverage is a rolling subset, not every Kalshi market: the seven crypto assets, prioritised by nearest expiry, with a bounded number of markets taped concurrently. Contact us if you need a specific series covered.

Sourced from the authenticated orderbook_delta WebSocket. kind is snapshot for the rows that anchor a book (so any window you request starts from complete state) or delta for a change. delta is the signed size change and size is the resting size at that price after it. seqis Kalshi's own sequence number for the capture session — it increments by exactly one across the session, so you can verify a stretch of tape is complete rather than take our word for it. A run of snapshot rows marks a new session, where it restarts. Narrow to one side with ?side=yes or ?side=no.

json
{ "data": [{
  "id": "1780774485780", "time": "2026-06-06 19:34:45.780",
  "received_ms": "1780774485791", "seq": "418823",
  "kind": "delta", "side": "yes", "price": 0.62, "delta": -85.0, "size": 240.0
}], "pagination": { … }, "meta": { … } }

Pages hold up to 10000 rows (default 1000) and are cursor-paginated like every other list endpoint. Many ticks routinely share one millisecond, so cursors carry a position within a timestamp — paginating a busy market never splits or drops the rows sharing a boundary instant.start_time/end_timeand your plan's history window apply as usual.

Binance — spot & futures

The underlying-asset book and trade tape from Binance. {venue} is spot or futures. The book is 20 levels per side.

GET/v3/{coin}/{venue}/latest
GET/v3/{coin}/{venue}/snapshots

Latest book / paginated book history. Each snapshot: price (mid), bids, asks, timestamp.

GET/v3/{coin}/{venue}/trades
GET/v3/{coin}/{venue}/trades/latest

1-second OHLCV candles with an aggressive buy/sell split — exact, 1:1 with the reference:

json
{ "data": [{
  "id": "1780747762000", "timestamp": "2026-06-06 12:09:22.000",
  "price_open": 61097.1, "price_high": 61097.1,
  "price_low": 61097.09, "price_close": 61097.1,
  "total_volume": 0.01667,
  "aggressive_buy_volume": 0.0156, "aggressive_sell_volume": 0.00107,
  "num_trades": 3
}], "pagination": { … }, "meta": { … } }
Binance doesn't deliver futures aggTrade over the websocket to our region (depth streams fine), so the futures 1s trade tape is polled from the REST aggTrades endpoint and aggregated to the same 1s OHLCV as spot. Spot and futures trades, and all books, are live.

Price bars — equities & crypto

OHLCV price bars for US-listed equities and ETFs, Binance spot pairs, and perpetual futures. One minute is the finest resolution; the coarser frames are rolled up from it.

GET/v3/bars

symbols is a comma-separated list. timeframe is one of 1Min, 5Min, 15Min, 1Hour or 1Day (default 1Min). start_time, end_time, limit and cursor behave exactly as they do on every other collection here.

id is a timestamp, not a unique key. The depth feed arrives every 100 ms and the exchange occasionally stamps two different books with the same millisecond, so a page can legitimately hold two rows with one idand two different ladders. Pagination handles the tie — the cursor is a keyset, so a shared millisecond is never split across a page boundary — but a client that keys rows by id alone will silently drop one of the two. Key on the row, or on (id, sequence within the page).

venue is optional and selects the market outright: us-equities, binance (spot) or binance-futures (perpetuals). Leave it off and a bare symbol is resolved for you — equities first, then Binance spot. Name it and you get that market or nothing, which is what you want when a string could be two instruments.

limit is bars per symbol. A request without one is not paged at all, because the natural bars pull is a whole window and a default page size would quietly hand back a short series — which is indistinguishable from a gap in the data. When you do page, every series is cut at the same instant and pagination.next_cursor resumes exactly there: no overlap, no gap.

bash
curl -H "X-API-Key: $DEPTHFEED_KEY" \
  "https://api.depthfeed.com/v3/bars?symbols=AAPL,BTCUSDT&timeframe=1Hour&start_time=2026-08-01T00:00:00Z"
json
{ "data": {
  "timeframe": "1Hour",
  "license_class": "source_attributed_public_serving",
  "attribution": "Market data via QuarterTrace. Source-attributed; not a consolidated (SIP) feed.",
  "series": [{
    "symbol": "AAPL", "timeframe": "1Hour", "assetClass": "equity",
    "source": "yahoo_finance", "derived": true,
    "t": [1785974400000, 1785978000000],
    "open": [306.83, 307.04], "high": [307.9, 308.1],
    "low": [306.1, 306.55], "close": [307.04, 307.8],
    "volume": [1043200, 887400]
  }]
}, "meta": { … } }

How to spell a symbol

Equities and ETFs are their ordinary ticker: AAPL, SPY, BRK-B. A crypto pair is spelled the way the venue spells it, so it is BTCUSDT rather than BTC. A perpetual future takes a .P suffix — BTCUSDT.P, AAPLUSDT.P — because the spot pair and the perp are different instruments trading at different prices, and a flat symbol space that quietly picked one for you would be picking which market your strategy measured.

Three strings are both a US ETF and a Binance pair: BTCU, ETHU and WLDU. Each resolves to the equity, decided against the symbol universe rather than your date range — so the answer never changes when you move the window. The crypto pair is still reachable under its own venue spelling.

Derived frames

Equities are collected at 1Min and 1Day; crypto is collected at 1Min. Anything else is aggregated on read, and a series says which it is with derived. An aggregated bar takes the first bar's open and the last bar's close, the extremes of the high and low, and the sum of the volume, over UTC-aligned buckets.

Limits

Three separate bounds, and they fail with three different codes. Your plan's history window applies here as it does everywhere else — past it, 403 HISTORY_LIMIT_EXCEEDED. Symbols per request is 3 on Explorer, 8 on Quant, 16 on Research, 32 on Desk; over that, 402 SYMBOL_LIMIT_EXCEEDED. And a single request may only span as much time as the frame's row count allows — 45 days at 1Min, 180 at 5Min, a year at 15Min and 1Hour, ten years at 1Day — over which it returns 400 WINDOW_TOO_WIDE. Page with limit and cursor to walk a range wider than one request carries.

A fourth response is not a limit but a moment: 503 BARS_BUSY means the bar service is at its concurrency ceiling right now. It carries details.retry_after in seconds, like a rate-limit refusal, and the same request usually succeeds on the next attempt — treat it as back off and retry, not as an outage.

One window, one meaning. Bars are bounded by the same plan history window as order-book depth, refused the same way past it (403 HISTORY_LIMIT_EXCEEDED, with oldest_allowed in the details). The open-source Strategy Builder reads this exact endpoint with your own key, so a backtest there reaches back exactly as far as your plan does here.
This data is source-attributed and is not a consolidated (SIP) feed, and it is not exchange-affiliated, NBBO or a direct exchange redistribution feed. Every response carries the attribution string and each series names its own source; both have to travel with the data if you display it.

Kalshi

Kalshi's crypto contracts — full yes/no depth the reference doesn't carry. 21 series for all 7 assets: 15-minute KX{ASSET}15M, threshold KX{ASSET}, and directional KX{ASSET}D. The threshold and directional series each run hourly, daily, and weekly markets concurrently — market_typecarries each market's actual window.

GET/v3/kalshi/markets

Filters: ?coin= (base asset), ?type= (window), and exact ?series= (for example KXBTC15M). For historical downloads, pass start_time and end_time here first and use the returned venue-native tickers. Do not generate Kalshi ticker ids from UTC timestamps; the venue's ticker clock convention changed historically.

GET/v3/kalshi/{ticker}/orderbook/latest

Newest full yes/no book for a ticker. yes/no are [price, size] arrays (prices in 0–1 dollars).

json
{ "data": {
  "ticker": "KXHYPED-26JUN0717-T73.9999", "series": "KXHYPED",
  "base_asset": "hype", "market_type": "24h",
  "id": "1780…", "timestamp": "…",
  "yes": [[0.62, 1400], …], "no": [[0.38, 900], …]
}, "meta": { … } }
GET/v3/kalshi/{ticker}/snapshots

The historical yes/no depth series for one exact discovered market ticker — same row shape, ordered by receive time, time-windowed + cursor-paginated (include_count opt-in, max 1000). The forward-captured Kalshi book history from a paced REST polling loop. Effective cadence varies with the active market set and API limits; it uses the same historical endpoint pattern as Polymarket and Limitless /snapshots, so Kalshi depth is backtestable too. Passing a series such as KXBTC15M, or an invented historical ticker, returns an explicit discovery error instead of a misleading empty page.

GET/v3/kalshi/{ticker}/candles

Hourly OHLC of the yes bid, the yes ask and the traded price, plus volume and open_interest. This is the only history that exists for most of the Kalshi catalog: Kalshi serves no historical order book, so a market that closed before our book capture began has no /snapshots and no /ticks — but it does have candles, reaching back up to a year.

Time-windowed and cursor-paginated like /snapshots(max 5000 rows), clamped to your plan's history window. ?interval= selects the candle period; only 1h is recorded, and any other value returns an explicit error naming what exists rather than an empty page. Candles are stored as recorded and never resampled, so a shorter period is never synthesised from a longer one.

price is null when nothing traded in the period — about 95% of them. The quote sides are always real, so a quiet hour still tells you where the book stood. Coverage is long-lived markets (open to close longer than 24 hours); short-dated crypto ladders are captured at full resolution by /snapshots and /ticks instead, and asking for their candles returns a 404 that says so.

json
{ "data": [{
  "ticker": "KXFEDDECISION-26JUL-H26", "series": "KXFEDDECISION",
  "market_type": "1w", "id": "1786168800000", "timestamp": "…",
  "period_minutes": 60,
  "yes_bid": { "open": 0.56, "high": 0.58, "low": 0.55, "close": 0.57 },
  "yes_ask": { "open": 0.64, "high": 0.65, "low": 0.62, "close": 0.63 },
  "price":   { "open": 0.60, "high": 0.61, "low": 0.59, "close": 0.61 },
  "volume": 240, "open_interest": 1180
}], "pagination": { … }, "meta": { … } }

Comparing all three venues at once? Start from the cross-venue screener instead of stitching per-venue calls.

Limitless

Limitless Exchange — a CLOB on Base running recurring up/down crypto markets at 5m and 15m. Depth is captured live over its order-book websocket (so even the fast 5m books are recorded). {slug} looks like btc-up-or-down-15-min-1780798512077.

GET/v3/limitless/markets

Currently-open slots. Filters: ?coin= and ?type= (5m/15m).

GET/v3/limitless/{slug}/orderbook/latest

Newest L2 depth for a slug (bids descending, asks ascending):

json
{ "data": {
  "slug": "btc-up-or-down-15-min-1780798512077", "condition_id": "0x…",
  "base_asset": "btc", "market_type": "15m",
  "id": "1780798…", "timestamp": "2026-06-07 02:27:09.532", "midpoint": 0.5,
  "bids": [[0.45, 200.0], …], "asks": [[0.46, 120.0], …]
}, "meta": { … } }
GET/v3/limitless/{slug}/snapshots

The historical depth series for one slug — same row shape, ordered by receive time, time-windowed + cursor-paginated. This is the forward-captured order-book history.

Binance prediction markets

Binance Wallet's Predict.fun-backed crypto prediction markets, normalized into a dedicated DepthFeed namespace. The initial catalogue covers BTC, ETH, and BNB up/down markets.

GET/v3/binance-prediction/markets

Filter with ?coin=btc, ?type=5m, or ?status=resolved. Results include the market/topic IDs, condition and token metadata, resolution, winning outcome, volume, liquidity, participant count, chain, and exchange contract.

GET/v3/binance-prediction/{market_id}/orderbook/latest
GET/v3/binance-prediction/{market_id}/snapshots

Latest and historical full L2 depth. The standardized BTC 15-minute lifecycle panel begins 26 June 2026; stable one-minute forward capture across tracked live markets begins 26 July. Earlier resting books cannot be reconstructed from settlement data.

GET/v3/binance-prediction/{market_id}/timeseries?metric=chance
GET/v3/binance-prediction/{market_id}/timeseries?metric=price
GET/v3/binance-prediction/{market_id}/timeseries?metric=last_trade
GET/v3/binance-prediction/{market_id}/trades

Probability, displayed price, and last-trade samples are kept separately and begin on 17 July 2026. The trades route is reserved while historical fills are reconstructed from BNB Smart Chain logs and currently returns an empty historical dataset.

Deterministic 5-minute and 15-minute catalogue, resolution, volume, and liquidity history is backfilled from 5 May 2026. The equivalent /v3/predictfun/* paths remain compatible aliases. Customers use a DepthFeed key; no Binance key or wallet pairing is required.

Cross-venue screener

Start here for anything cross-venue. The screener answers the core question — how is the same “up or down” market priced on Polymarket, Kalshi, and Limitless right now? — in a single request, so you never stitch the three per-venue endpoints (with their three different identifiers) together yourself. Every plan sees all three venues; the plan history window is the only difference when you follow a cell into its history.

GET/v3/screener

The grid: one cell per asset with each venue's latest “up” probability, best bid/ask, and settlement reference. A placeholder/empty book reports null rather than a meaningless 0.50.

json
{ "data": {
  "assets":  ["btc","eth","sol","xrp","doge","bnb","hype"],
  "windows": ["5m","15m","1h","4h","24h"],
  "cells": [{ "asset": "btc",
    "polymarket": { "up": 0.735, "down": 0.265, "best_bid": 0.73, "best_ask": 0.74, "price_to_beat": 62764.0 },
    "kalshi":     { "up": 0.71,  "down": 0.29,  … },
    "limitless":  { "up": 0.75,  "down": 0.25,  "slug": "btc-up-or-down-…", … } }],
  "paid": true
}, "meta": { … } }
GET/v3/screener/{asset}/{window}

Drill into one (asset, window): the full L2 book on every venue side by side (books.polymarket / books.kalshi / books.limitless), plus the underlying spot series and the settlement price_to_beat — the single call a cross-venue backtest or arb screen actually needs.

GET/v3/overviewno auth

Per-asset, cross-venue time-series overview (price segments per window) — what powers the dashboard overview chart. Public, no key required; select with ?asset=.

Batch

POST/v3/batch

Run up to 10 GET /v3/ requests in one round-trip. Each sub-request is dispatched independently and gets its own status.

shell
curl -X POST https://api.depthfeed.com/v3/batch \
  -H "Authorization: Bearer df_your_key" -H "Content-Type: application/json" \
  -d '{ "requests": [
    { "id": "a", "method": "GET", "path": "/v3/btc/markets?limit=1" },
    { "id": "b", "method": "GET", "path": "/v3/limitless/markets?type=5m&limit=1" },
    { "id": "c", "method": "GET", "path": "/v3/bars?symbols=BTCUSDT&timeframe=1Hour" }
  ] }'

A contract's depth and the underlying's price bars in one round-trip is the pairing this exists for — /v3/bars is a sub-request like any other.

Returns { data: [ { id, status, body }, … ] } with an outer 200. Over 10 sub-requests → 400 BATCH_TOO_LARGE; a non-GET or non-/v3/ path gets a per-sub 400 BATCH_INVALID_PATH.

Sports

Realtime sports prediction-market data — live order-book depth on Polymarket and Kalshi, alongside sportsbook odds, live scores, and an injury feed across the major leagues (NBA, NFL, MLB, NHL, soccer, tennis, UFC, F1 and more). Both venues are included together on every tier; plans change the shared history window, throughput, and live-book concurrency. Same{ data, meta, pagination } envelope, auth, and cursor pagination as the rest of the API.

Discovering a market. Walk the funnel: /v3/sports/leagues/v3/sports/markets?league=… → take a market's tokens[](each is one outcome's asset_id) → /v3/sports/books/{asset_id}/latest for the live book. Or start from /v3/sports/overview, which answers “what's trading now, on every venue” in one call.

GET/v3/sports/overview

Start here. The live cross-sport board — every tracked league with its in-progress and upcoming games, current prices, and best bid/ask per venue, in a single request.

GET/v3/sports/leagues

The leagues we track, each with its game and market counts — the top of the discovery funnel.

GET/v3/sports/markets

Sports markets across Polymarket and Kalshi. Filter with ?league= (e.g. nba, mlb, soccer); cursor-paginated like every list endpoint (?limit=, ?cursor=). Each market carries its condition_id, market_slug, outcomes, and tokens — the per-outcome asset_ids you hand to the book endpoint.

json
{ "data": [{
  "condition_id": "0xfb53…", "market_slug": "mls-nyc-laf-…-total-2pt5",
  "league": "soccer", "kind": "totals", "title": "New York City FC vs. Los Angeles FC",
  "outcomes": ["Over", "Under"],
  "tokens": ["45485059…", "38846626…"],   // one asset_id per outcome
  "game_start": "2026-11-05T00:30:00Z", "resolved": false
}], "pagination": { "next_cursor": "…", "has_more": true, "limit": 50 }, "meta": { … } }
GET/v3/sports/books/{asset_id}/latest

Newest recorded full order book for one outcome — the asset_idis a token from the market'stokens[]. The ladder arrives as four index-aligned arrays (bid_prices with bid_sizes, ask_prices with ask_sizes) rather than the [price, size] pairs the crypto routes return; What every book route shares lists every difference. For latency-sensitive live use, Quant and higher tiers can subscribe to the sports WebSocket channels below.

The book routes take a token, not a condition_id. A market has one condition_id and one entry in tokens[] per outcome, and the book belongs to the outcome. On Kalshi rows the two strings happen to be equal, so code that reads condition_id works on Kalshi and then returns 404 SNAPSHOT_NOT_FOUND on every Polymarket market. Read tokens[].
GET/v3/sports/books/{asset_id}/history

Historical full-ladder snapshots for the outcome. Page with ?limit= and ?cursor=; bound time with ?start_time= / ?end_time= (the shorter ?from= / ?to= aliases are also accepted). Add ?interval=1m to return the latest snapshot in each epoch-aligned bucket; supported units are s, m, h, and d, from one second through one day. Omit it, or useraw / tick, for every captured snapshot. Sports depth is forward-captured: the Polymarket archive starts 14 April 2026 and the Kalshi archive starts 12 June 2026.

Interval downsamples; it does not interpolate. A bucket exists in the response only if the book was captured inside it, so a two-hour window at ?interval=1m spans 120 buckets but returns only the minutes that held a capture. For an outcome that is quoted infrequently — a market still days from tip-off — that is commonly ten to twenty rows, and it is the full tape for the window rather than a truncated page. Every response that used ?interval= carries a resolution block so the two are distinguishable:

json
{ "data": [ … ],
  "pagination": { "count": 13, "has_more": false, "limit": 500 },
  "resolution": {
    "interval_secs": 60,
    "buckets_in_range": 121,   // minutes the window spans
    "buckets_filled": 13,      // minutes that held a capture
    "complete": true           // false while a next_cursor is outstanding
  } }

If buckets_filled is far below buckets_in_range, the outcome is quoted infrequently — ask for a coarser interval, or omit it and read every capture. Capture density tracks trading activity: an outcome on a game in progress records hundreds to thousands of snapshots over two hours, while the same outcome three weeks out records a few dozen. So ?interval=1m returns a near-complete minute series once a game is underway, and a short one for a market that is not yet being traded.

Add ?fill=ffill alongside interval for the dense grid instead: one row per bucket for the whole window, carrying the last captured book into the minutes that held no capture, each row marked filled and stamped as_of_ts with the moment that book was recorded. Same parameter, same meaning, same markers as every other snapshot route — see Filling the gaps.

This route returns newest first, and it is the only book route that does. The crypto and venue snapshot routes return oldest first. Sort by the row timestamp rather than relying on arrival order if you read both.

Games and resolved market context

GET/v3/sports/{league}/games

Live games and scores for a league (e.g. nba, nfl, soccer/eng.1), each with its linked Polymarket & Kalshi markets.

GET/v3/sports/games/{game_slug}

The joined game document: schedule and final score, linked prediction markets and settlements, sportsbook lines, and injury context. Use the companion routes below when you need an individual high-volume layer.

GET/v3/sports/games/{game_slug}/markets
GET/v3/sports/games/{game_slug}/lines
GET/v3/sports/games/{game_slug}/box
GET/v3/sports/games/{game_slug}/plays
GET/v3/sports/games/{game_slug}/winprob

Game markets, line movement, box scores, play-by-play, and per-play win probability.

These four need an ESPN match. Everything except /markets is keyed to an ESPN event, so a game we could not match returns 404 DATA_NOT_FOUND with “game is not matched to an ESPN event”— the game itself and its prediction markets are still served. Check espn on /v3/sports/games/{game_slug}: nullmeans the four routes below will 404 for it. Match rate is a property of the league, not of the game (measured 2026-09-02): NHL 97%, NBA 91%, NFL 68%, WNBA 67%, EPL 66%, MLB 59%, UFC 56%, CFB 49% — and near zero for esports, cricket, darts and rugby, where no ESPN feed backs the catalog. Sort your candidates by league before building a pipeline on box scores.
GET/v3/sports/markets/{condition_id}

One normalized market with venue identifiers, outcomes, token IDs, game join, and settlement state.

Players, props, and injuries

GET/v3/sports/{league}/players

Search the league athlete directory and retrieve stable athlete IDs used by player-prop joins.

GET/v3/sports/players/{athlete_id}
GET/v3/sports/players/{athlete_id}/injuries
GET/v3/sports/players/{athlete_id}/box
GET/v3/sports/players/{athlete_id}/markets

Player profile, injury change-log, box-score game log, and entity-resolved prediction-market props.

GET/v3/sports/{league}/injuries

The live injury feed per league — the pre-game status changes that move the market.

Sportsbook odds

GET/v3/sports/odds/lines

Live moneyline, spread, total, and prop lines across books, normalized for comparison with the market price.

GET/v3/sports/odds/books

The active sportsbook catalog and source freshness.

GET/v3/sports/{league}/odds-history

Historical sportsbook lines for four leagues: nfl (nflverse, seasons 1999–2026) and epl, la-liga, soccer (football-data.co.uk, 2000/01 onward). Any other league returns 404 SPORTS_UNKNOWN_ROUTE naming these four. A request needs at least one filter: NFL takes season, team, from, or to; soccer takes a football-data season code (for example 2324) and/or team. Without one you get 400 INVALID_PARAMETER saying which.

Your plan’s history window clips this route too. The seasons above describe what is stored; what is returned is bounded by the same 7 / 30 / 90-day (or full-archive) window as order-book history, applied to the game date. On Explorer, Quant or Research a request for an old season returns 200 with an empty dataarray rather than an error, because every row fell outside the window — not because the season is missing.
GET/v3/sports/odds/stream

Server-Sent Events for changing sportsbook lines. Send Accept: text/event-stream; events pass through as they arrive and the connection remains open. Sports order books use the main DepthFeed WebSocket below.

Injury webhooks

POST/v3/sports/webhookspaid plan

Register an HTTPS receiver with league and injury-status filters. The signing secret is shown once. Deliveries include X-DepthFeed-Event and X-DepthFeed-Signature; verify the HMAC-SHA256 signature over the exact raw request body before parsing JSON.

shell
curl -X POST "https://api.depthfeed.com/v3/sports/webhooks" \
  -H "Authorization: Bearer df_your_key" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/depthfeed","leagues":["nba","nfl"],"statuses":["out","questionable"]}'
GET/v3/sports/webhookspaid plan
POST/v3/sports/webhooks/{sub_id}/testpaid plan
DELETE/v3/sports/webhooks/{sub_id}paid plan

List subscriptions and delivery health, send a signed test event, or revoke a subscription.

shell
# discover a market, then pull its live book
curl "https://api.depthfeed.com/v3/sports/markets?league=mlb&limit=1" -H "Authorization: Bearer df_your_key"
# take tokens[0] from the response, then:
curl "https://api.depthfeed.com/v3/sports/books/<asset_id>/latest" -H "Authorization: Bearer df_your_key"

Live stream (WebSocket)

GET/v3/stream

A WebSocket that pushes live order books and prices as they change. Frame payloads are the same JSON objects the REST snapshot endpoints return, so code written against REST transfers directly. Authenticate at connect time with the X-API-Key header, Authorization: Bearer, or — for browsers, which cannot set WebSocket headers — ?api_key=df_….

shell
# wscat -c "wss://api.depthfeed.com/v3/stream?api_key=df_your_key"
> {"op":"subscribe","channels":["book:polymarket:btc:2510645","price:btc"]}
< {"op":"subscribed","channels":[…],"active":2}
< {"channel":"price:btc","data":{"coin":"btc","price":63560.1,…},"ts":1781300671083}
< {"channel":"book:polymarket:btc:2510645","data":{…same shape as REST snapshot…},"ts":…}

Channels

ChannelPayloadNotes
book:polymarket:<coin>:<market_id>REST snapshot object (with orderbooks)Tick-level via our CLOB websocket tap
book:kalshi:<coin>:<ticker>Same shape as /v3/kalshi/…/snapshots rowsUpdates from the paced Kalshi polling loop
book:limitless:<coin>:<slug>Same shape as /v3/limitless/…/snapshots rowsPush-based (socket.io tap)
book:binance:<coin>:<spot|futures>Same shape as /v3/…/latest100ms depth feed
book:sports:polymarket:<token_id>Full sports ladderQuant+: first-party, event-driven CLOB stream
book:sports:kalshi:<ticker>Full sports ladderQuant+: best-effort Kalshi order-book delta websocket, relay fallback; change-only frames
price:<coin>{ coin, price, source, venue, id, time }Binance underlying, ~4 ticks/s max

Ops: subscribe · unsubscribe (each takes {"channels":[…]}, applied atomically) and pingpong. A trailing * market segment (e.g. book:kalshi:btc:*) subscribes a whole venue/coin — Desk plan only. Op-level problems (bad channel, over your subscription cap) come back as {"op":"error","code":…} frames and never drop the connection.

Sports channels use concrete identifiers and do not support wildcards. Quant, Research, and Desk allow one, two, and three concurrent sports books respectively. Every sports frame includes the complete ladder, venue, asset_id, exchange/receive timestamps, full_ladder: true, and adelivery field naming the source of each frame: polymarket_clob_ws,kalshi_orderbook_delta_ws, or scoretape_relaywhen the sports relay is serving. Kalshi sports books are driven by the authenticated Kalshi order-book delta websocket, with the relay as fallback, so frame timing follows exchange activity rather than a fixed polling interval. Read freshness from each observation's timestamps rather than assuming a refresh rate.

shell
> {"op":"subscribe","channels":[
    "book:sports:polymarket:<token_id>",
    "book:sports:kalshi:<market_ticker>"
  ]}
< {"op":"subscribed","channels":[…],"active":2}

Per-plan limits

PlanConnectionsSubscriptionsChannels
Explorer11Every venue, all coins; no sports book
Quant15Every venue, all coins; 1 sports book
Research225Every venue, all coins; 2 sports books
Desk5100Every venue, all coins, plus wildcards; 3 sports books

The channel column is the same on every row for a reason: a plan does not buy a venue or a coin here either. Explorer streams any book on any of the four venues — it just holds one subscription at a time, so a second channel in the same subscribe returns SUB_LIMIT and nothing is applied. Wildcards and live sports books are the two things a subscription cap alone does not cover.

Op-error frames

These arrive as {"op":"error","code":…,"message":…} and leave the connection open. They are frame codes, not HTTP statuses.

codeMeaning
CHANNEL_INVALIDNot a valid channel name. The shapes are book:<venue>:<coin>:<market> and price:<coin>.
UNKNOWN_OPNot one of subscribe, unsubscribe, ping.
SUB_LIMITThe subscribe would exceed your plan's subscription cap. Nothing was subscribed — the op applies atomically.
SPORTS_SUB_LIMITThe same, for the separate concurrent-sports-book cap.
SPORTS_NOT_IN_PLANYour plan has no low-latency sports book subscription.
VENUE_NOT_IN_PLANReturned only on the anonymous demo surface. Every account plan streams every venue.
WILDCARD_NOT_IN_PLANA trailing * market segment is Desk only. Name the markets instead.
SLOW_CONSUMERYour connection has fallen too far behind the feed and frames are being dropped. Read faster, or subscribe to fewer channels.

Close codes & reconnecting

CodeMeaning
4001Authentication failed (missing / unknown / revoked key)
4003Plan connection limit reached
4029Op flooding — slow down
1013Key verification temporarily unavailable — retry shortly

The server heartbeats with protocol pings every 25s. Deploys and failovers drop connections by design — treat disconnect → reconnect with exponential backoff (250ms doubling to ~5s) and re-send your subscriptions as the normal lifecycle, not an error path.

Freshness, measured

Median capture→client delivery measured from an external client through our edge: Polymarket ≈10ms (p95 ≈30ms), Binance and Limitless sub-100ms, underlying price ≈110ms. Kalshi follows its REST capture cadence, ≈1s median. Numbers come from deploy/stream_freshness.py runs against production — we publish what we measure, nothing rounder.

Streamed messages are metered into your usage like REST responses. Subscriptions are limited per plan, and quotas are per user across all connections. A scoped, automatically expiring 7-day Desk evaluation is available on request for production-latency validation.

Wallet Intelligence

Wallet Intelligence is a read-only research workspace for public Polymarket wallets. Save a watchlist, discover traders from Polymarket's public leaderboard, and review public positions, recent activity, realized samples, and portfolio-level metrics in one place. It never connects a wallet, signs a transaction, or places a real order.

Next-book execution audit

For each eligible public trade, DepthFeed finds the first Polymarket book it recorded after the activity timestamp, walks the displayed ladder for the same number of shares, and reports the replay price, fillable fraction, observed delay, and signed price difference versus the public trade. The lookup window is 90 seconds. A missing match is shown as missing; it is never estimated.

Grades are mechanical: Depth remainedmeans at least 95% of the public size was displayed within 1¢ of the trader's price; Price moved means at least 95% remained but at a difference above 1¢; Size unavailable means less than 95% of the public size was displayed.

Forward paper copy

Start a paper mirror from any inspected wallet. Only public trades observed after the mirror is created are eligible: DepthFeed deduplicates each fill, applies the configured copy ratio and per-trade cap, then fills it at the next available live quote in the normal Paper Trading ledger. Cash, positions, P&L, the equity curve, circuit breaker, and settlement history appear under Paper Trading.

An optional HTTPS callback receives each successful copied fill. Its signing secret is shown once. Verify HMAC-SHA256(timestamp.body) against x-depthfeed-signature; the timestamp is sent in x-depthfeed-timestamp. Local, private-network, credential-bearing, non-HTTPS, and redirect destinations are rejected.

PlanSaved walletsAudited trades per refreshPublic activity rows
Explorer1330
Quant510100
Research1025250
Desk2050500

Interpretation limits

  • A public address may be only one wallet in a trader's portfolio and does not prove identity or ownership.
  • Public activity timestamps are not private order timestamps. The audit measures what DepthFeed observed after publication, not the trader's original latency.
  • Only trades in DepthFeed's recorded Polymarket coverage can be matched. Unsupported or uncaptured markets are shown without an audit.
  • The replay consumes displayed size mathematically; it cannot model hidden liquidity, queue priority, cancellation races, fees, or the market impact of a real follower order.
  • Leaderboard rank, P&L, positions, and activity come from Polymarket's public data services and may be delayed, revised, incomplete, or temporarily unavailable.
  • Wallet Intelligence and its paper mirror are research tooling—not live copy trading, investment advice, a recommendation, or a guarantee of execution.
Only watchlist addresses and user-authored labels are stored by DepthFeed. Public wallet data is fetched on demand and briefly cached; execution audits are computed against our recorded order books at request time.

Wallet research guides

Start with the Polymarket wallet tracker guide, then compare wallet analytics, leaderboard research, and copy-trading execution analysis.

Backtest Lab

The Backtest Lab (in your dashboard) backtests entry/exit strategies on resolved native Polymarket, directional Kalshi, and Limitless crypto markets — settlement-anchored, so every simulated trade has a known outcome. It runs on the same order-book history the API serves, within your plan's history window.

Venues and sampling

Polymarket supports 5m, 15m, 1h, 4h and 24h windows. Limitless supports captured 5m and 15m up/down contracts. Kalshi supports the captured directional 15m, 1h and 24h series; range buckets are excluded because YES does not mean UP. Kalshi defaults to one representative directional contract per event so a ladder of correlated strikes is not counted as independent evidence. Advanced users can inspect all strikes with an explicit correlation warning.

Strategies

Five built-in presets (late-favorite, spot-leads, dip-reversion, level-cross, cheap-lotto) plus a custom JavaScript editor: your script plans entries over each market's price path and runs sandboxed in a web worker — no network, hard time budget, stored locally in your browser and exportable as JSON.

Fill models

ModelEntry price
MidThe snapshot mid — fastest, most optimistic.
Mid + slippageMid shifted by a fixed slippage you set.
Book-depth VWAPWalks the real ask ladder level by level. Missing or one-sided depth is unfillable; it never falls back to a modeled midpoint.

Fees are included in sizing and net P&L. Polymarket and Limitless use an editable flat per-share assumption; Kalshi uses its non-linear expected-earnings formula with an editable rate and cent rounding per order.

Portable exports

Export the complete trade log as CSV, the strategy and execution assumptions as portable JSON, or a reproducible run bundle containing the exact tested sample, statistics, risk report, equity curve and trades. Exports never embed raw historical paths or order-book archives.

Position sizing

How much each trade stakes — a flat amount, or a fraction of a compounding bankroll:

SchemeStake per trade
FixedA flat dollar amount — simple, but the only scheme that can trade a bankroll to zero.
FractionalA constant fraction of your live bankroll (fixed-fractional). It compounds and can't reach zero from sizing alone.
KellyGrowth-optimal f* = (q − p)/(1 − p) for a $1 binary at price p, scaled by a fractional-Kelly multiplier (half-Kelly = the standard hedge) and capped per trade. The win probability q comes from a fixed edge, a fixed win probability, or the strategy's own trailing hit rate. No edge (f* ≤ 0) ⇒ the entry is skipped.

Risk of ruin

Every run reports a risk panel derived from its own trades: the optimal fraction your edge can support (Ralph Vince optimal-f), a recommended max (half of it), a Monte-Carlo risk of ruin (the chance the bankroll ever falls to ≤30% of its start), and an over-bettingwarning when you're staking past the growth-optimal point — where more risk buys less long-run growth. Deploying to paper auto-caps the live per-trade size at this safe fraction.

Results: trade log, win rate, ROI, Sharpe, max drawdown, the equity curve, and the risk-of-ruin panel. Paid plans can save up to 50 frozen runs (they re-render identically later, no data re-reads), and the built-in AI assistant helps write and debug strategies and interpret sizing and risk within a plan-metered allowance.

When a backtested rule looks good, deploy it to Paper Trading to forward-test it on live markets with virtual cash.

Paper trading

Paper Trading runs strategies forward on livemarkets with virtual cash. Fills, marks and settlement are all priced from DepthFeed's live order books — buys at the ask, sells at the bid, positions settle to $0/$1 automatically when the market resolves. Each strategy gets its own equity curve, positions view, and full trade log.

Three ways to trade

ModeHow it works
WebhookYour own system (bot, cron, notebook) POSTs buy/sell signals to a per-strategy token URL. Your logic stays yours — we do execution, accounting and charts.
Lab ruleDeploy a Polymarket or Kalshi Backtest Lab preset (time-window entry, level cross, dip reversion) with venue-native sides, take-profit/stop-loss, a max-open cap, and position sizing (fixed / fractional / Kelly); it's evaluated server-side against live quotes (~90s cadence).
Wallet copyMirror new public fills from an inspected Polymarket wallet with a copy ratio and per-trade cap. Results use the same paper ledger and may optionally emit signed HTTPS fill webhooks.

Signal webhook

POST/paper/hook/{token}no auth

The token (shown once at create/rotate) is the auth. Fills always price from the live book at signal time — a payload can never set its own price. Markets are identified by venue + market: a Kalshi ticker, or a Polymarket CLOB token id. You can trade any market on the venue, not just the ones DepthFeed records — if we don't have it locally, the fill is priced from a live read of the venue's own order book.

shell
curl -X POST 'https://api.depthfeed.com/paper/hook/pt_your_token' \
  -H 'content-type: application/json' \
  -d '{
    "action": "buy",              # buy | sell | close
    "venue": "polymarket",        # polymarket | kalshi
    "market": "1234567890...",    # CLOB token id / Kalshi ticker
    "side": "yes",                # kalshi: yes|no (polymarket tokens are one-sided)
    "usd": 100,                   # size by dollars… or "shares": 150
    "client_id": "sig-0042"       # optional idempotency key (safe retries)
  }'
json
{ "data": { "filled": true, "action": "buy", "price": 0.62,
            "shares": 161.29, "cost_cents": 10000, "cash_cents": 990000,
            "position_id": "…" } }
ErrorMeaning
NO_FRESH_QUOTENo live book for that market — a bad id, or the market has closed / gone one-sided at 0/1 (settlement closes positions automatically).
INSUFFICIENT_CASHThe order exceeds the strategy's paper cash.
NO_POSITIONsell/close with no open lot on that market/side.
RATE_LIMITEDOver 120 signals/min for the strategy.
PAUSEDThe strategy is paused in the dashboard.

Circuit breaker

Each strategy has a trailing max-drawdown stop (default 60%, editable; set to 0 to disable). If live equity ever falls that far below its high-water mark, the strategy is automatically flattened at current marks and halted — a hard stop against a strategy trading itself toward zero. Resume it any time; resuming resets the high-water mark to your current equity.

Limits

Concurrent strategies per plan: Explorer 1 · Quant 5 · Research 15 · Desk 40. Up to 100 open positions per strategy; starting balance $100–$1,000,000 (default $10,000). Marks refresh on a ~90-second cadence — this is a forward-testing tracker, not an execution venue.

Paper results use real displayed liquidity for pricing but don't consume it — live fills of size may see more slippage than the paper fill at top-of-book. Markets we don't record locally are priced from a live venue read and won't re-settle from our resolution data — they close at their last mark after a few days if the venue doesn't resolve them through a path we track.

What every book route shares

Every order-book route — six crypto and venue routes plus sports — answers the same contract, so code written against one ports to the next by changing the path and the identifier.

  • The { data, pagination, meta } envelope and meta.request_id.
  • Keyset cursor pagination and limit.
  • The window, as start_time/end_time or from/to.
  • interval for one book per bucket, and fill=ffill for a dense grid.
  • filled, as_of_ts, and as_of_ts_ms on filled rows.
  • The resolution block on any response that used interval.
  • The plan history window, and the error.code enum.

Four things still differ, all of them visible in the response rather than hidden in behaviour.

Crypto & venue routesSports history
Row timestamptime or timestamp as a string, id as ms-epochts as ISO-8601, ts_ms as ms-epoch
OrderOldest firstNewest first
LadderIncluded, except the per-coin event route, which needs include_orderbook=trueAlways included
Level shapePairs: bids / asks as [[price, size], …], best first (Kalshi names them yes / no)Parallel arrays: bid_prices with bid_sizes, ask_prices with ask_sizes, index-aligned

These are the shapes existing integrations are built on, so they stay as they are. Sort on the row’s own timestamp rather than on arrival order and the difference stops mattering.

Venues & coverage

Precision comes from sourcing the same upstreams the markets use, so values match by construction.

VenueCaptureWindowsNotes
PolymarketCLOB websocket — every book + price-change event5m · 15m · 1h · 4h · 24hFull depth, event-driven. Settlement open/close from Polymarket's own metadata.
KalshiPublic REST, paced full-depth polling15m · 1h · 24h · 1wYes/no book, up to 100 levels/side, 7 assets; effective cadence varies with market load and API limits.
Limitlesssocket.io websocket (orderbookUpdate)5m · 15mCLOB on Base; live push captures the fast 5m books REST can't snapshot.
Binancespot + futures @depth20@100ms + @aggTradeUnderlying book + 1s OHLCV, the precision anchor for the up/down markets.

All 7 assets (btc · eth · sol · xrp · doge · bnb · hype) across every venue that lists them.

Data model & schema

Timestamps are epoch-millis. exch_ts_ms = exchange time, recv_ts_ms = our receive time (the difference is capture latency). Below are the STORED columns. The crypto and venue routes zip the four price/size arrays into bids / asks pairs before returning them; the sports routes pass them through as the four arrays. Everything else reaches you under the name it is stored under.

polymarket_book — full order-book snapshots

asset_id, condition_id, slug, base_asset, market_type, exch_ts_ms, recv_ts_ms, hash, bid_prices[], bid_sizes[], ask_prices[], ask_sizes[]

polymarket_changes — deltas for replay between snapshots

asset_id, condition_id, exch_ts_ms, recv_ts_ms, side, price, size

polymarket_markets — one row per token

asset_id, market_id, event_id, condition_id, slug, outcome, base_asset, market_type, clob_token_up, clob_token_down, resolution_source, start_time, end_time, first_seen_ms

polymarket_reference — settlement

slug, base_asset, market_type, start_ms, end_ms, price_to_beat, final_price, winner, final_volume, final_liquidity, resolved_at, captured_ms

binance_book — 20-level book (spot & futures)

venue, symbol, exch_ts_ms, recv_ts_ms, bid_prices[], bid_sizes[], ask_prices[], ask_sizes[]

binance_trades_1s — 1s OHLCV

venue, symbol, second_ms, open, high, low, close, volume, buy_volume, sell_volume, trades, recv_ts_ms

prices — unified underlying-price series

asset, source (binance|chainlink), venue, exch_ts_ms, price

kalshi_book / kalshi_markets

kalshi_book: ticker, series, base_asset, market_type, recv_ts_ms, yes_prices[], yes_sizes[], no_prices[], no_sizes[]

kalshi_markets: ticker, event_ticker, series, base_asset, market_type, title, status, strike, open_time, close_time, first_seen_ms

limitless_book / limitless_markets

limitless_book: slug, condition_id, base_asset, market_type, recv_ts_ms, midpoint, bid_prices[], bid_sizes[], ask_prices[], ask_sizes[]

limitless_markets: slug, condition_id, base_asset, market_type, title, status, expiration_ms, first_seen_ms

History & coverage

Every plan window is served from the live store plus the cold archive, so the full window is always available. Desk has no rolling history cap: it can query every recorded snapshot from the applicable series floor through the live feed. The floors differ because contracts and licensed capture did not begin on the same day for every asset/window combination.

Polymarket full-depth archive floors (UTC)

A floor is the first day a ladder was recorded, which is not the first day of an unbroken series. The Polymarket archive holds nothing before 2026-01-03, and January is five scattered days (2026-01-03 to 01-06, and 01-24). Continuous daily coverage begins 2026-02-12. A backtest that needs an unbroken series should start there.

Assets5m15m1h4h24h
BTC2026-02-122026-01-032026-01-032026-01-032026-01-03
ETH, SOL, XRP2026-02-182026-01-032026-01-032026-01-032026-01-03
BNB2026-03-132026-03-132026-03-062026-03-132026-03-07
DOGE2026-03-132026-03-132026-03-112026-03-132026-03-11
HYPE2026-03-132026-03-132026-03-062026-03-132026-03-07

These are the earliest days with recorded full ladders, not a claim that every asset/window existed before its floor. A snapshot returns the complete recorded ladder; it can legitimately contain no levels when the market itself had no resting orders at that instant.

Kalshi archive floors (UTC)

Kalshi floors are separate from the Polymarket ones above and are later. The earliest recorded Kalshi ladder of any series is 2026-01-08. For the 15-minute crypto series:

SeriesEarliest recorded ladder
KXBTC15M, KXETH15M2026-01-08
KXSOL15M2026-01-09
KXXRP15M2026-02-11
KXBNB15M, KXDOGE15M2026-03-07
KXHYPE15M2026-03-18

Hourly, daily and weekly Kalshi series carry their own per-series floors, and BTC, ETH, XRP and DOGE begin on 2026-01-08 there too. Rather than publish a floor per series, treat discovery as authoritative: /v3/kalshi/markets?series=KXBTC15M&start_time=…&end_time=… lists exactly the tickers whose depth is retrievable in that range, and Kalshi ticker ids are venue-defined, so they must be discovered rather than constructed from UTC timestamps.

Kalshi capture cadence and depth are not constant across the archive. Current capture polls the full ladder at up to 100 levels per side every few seconds; earlier periods are sparser, and some spring 2026 days carry only a handful of observations per contract. Snapshot rows are stored as recorded, so a research pull spanning the archive should resample onto a fixed grid and carry an observation count per bucket rather than assume a constant sampling interval.

Order-book depth is captured live and cannot be recreated after the fact. Archive access is delivered through the documented metered query surfaces; DepthFeed does not provide customer-requested reconstruction or bulk archive delivery.

Code examples

Walk a market's full depth history (Python)

python
import requests

BASE = "https://api.depthfeed.com/v3"
H = {"Authorization": "Bearer df_your_key"}

# 1. find a market
m = requests.get(f"{BASE}/btc/markets?type=15m&limit=1", headers=H).json()["data"][0]
mid = m["market_id"]

# 2. page through its order-book snapshots
cursor, rows = None, []
while True:
    q = f"{BASE}/btc/markets/{mid}/snapshots?include_orderbook=true&limit=1000"
    if cursor: q += f"&cursor={cursor}"
    page = requests.get(q, headers=H).json()
    rows += page["data"]
    if not page["pagination"]["has_more"]: break
    cursor = page["pagination"]["next_cursor"]

print(len(rows), "snapshots")

Latest cross-venue depth for BTC up/down (JavaScript)

javascript
const h = { Authorization: "Bearer df_your_key" };

// One call → the full BTC 15m book on all three venues at once.
// (No stitching: the screener is the cross-venue entry point.)
const res = await fetch("https://api.depthfeed.com/v3/screener/btc/15m", { headers: h }).then(r => r.json());
const { books } = res.data;        // books.polymarket / books.kalshi / books.limitless

AI agents & LLMs

DepthFeed is built to be driven by agents: plain JSON over HTTPS, one Bearer key, stable error codes, and no SDK required. A machine-readable product summary lives at /llms.txt.

As a function-calling / MCP-style tool

Wrap any endpoint as a tool — the API is self-describing enough that one generic definition covers most workflows:

json
{
  "name": "depthfeed_get",
  "description": "Query DepthFeed prediction-market order-book data. Paths: /v3/{coin}/markets, /v3/{coin}/markets/{id}/snapshots, /v3/kalshi/markets, /v3/kalshi/{ticker}/snapshots, /v3/kalshi/{ticker}/candles, /v3/limitless/{slug}/snapshots, /v3/{coin}/{spot|futures}/latest. Coins: btc eth sol xrp doge bnb hype.",
  "parameters": {
    "type": "object",
    "properties": { "path": { "type": "string", "description": "GET path under /v3, with query params" } },
    "required": ["path"]
  }
}

The tool body is a single authenticated GET:

shell
curl "https://api.depthfeed.com{path}" -H "Authorization: Bearer df_your_key"

Live data in agents

The WebSocket stream uses three JSON ops (subscribe / unsubscribe / ping) and authenticates with ?api_key=where headers can't be set — trivial to drive from any runtime. Frames carry the same JSON objects as REST, so an agent's parsing code is identical for history and live.

Tip for assistants: /v3/whoamiechoes the key's plan, rate limits, and history window — call it first and self-configure instead of guessing limits.

Status & changelog

What's live, newest first:

  • Live streaming. /v3/stream WebSocket — order-book and price channels on every venue, ~10ms median Polymarket delivery, plan-gated subscriptions.
  • Limitless venue. Markets, latest orderbook, and historical depth snapshots — with 5m + 15m captured live over the order-book websocket.
  • Kalshi expanded. 21 series for all 7 assets (15-minute, threshold, and directional — the latter two running hourly/daily/weekly concurrently); market_type exposed and filterable.
  • Full v3 API. Polymarket markets + snapshots, Binance spot/futures, batch, cursor pagination, per-plan history enforcement, metered usage.
  • Public HTTPS. https://api.depthfeed.com over TLS.

Order-book depth fills forward from capture start and cannot be back-filled — history matures toward your plan window over calendar time. Questions or a backfill request? Get in touch.

Start backtesting Polymarket & Kalshi on real depth.

Free to start, no card. Upgrade when your strategy is ready for the full book.

Start free