0DTESPX.com

Rate limits

The market-data credit budget and per-endpoint costs.

Rate limits keep the full historical archive fast for everyone by stopping bulk scrapers from crowding out normal traffic. The budget is deliberately generous — interactive trading, charting, research, and ordinary integration work essentially never see a 429; this page only matters if you pull large amounts of data in tight automated loops.

How the budget works

Every account has a single leaky bucket of 10,000 credits. Each metered request adds its cost to the bucket, which leaks back toward empty over 24 hours (~0.116 credits per second — a full bucket drains in a day). If a request would push the bucket past 10,000 credits, it's rejected with 429.

The cap is a capacity, not a daily allowance: it's the most in-flight usage you can hold at any one instant, and it refills continuously as it drains. A 429 carries where you stand:

{ "error": "rate_limit_exceeded", "retry_after_seconds": 60, "credits_used": 3030, "credits_cap": 10000 }

credits_used can be well below credits_cap and still 429 — what matters is whether this request's cost fits in the remaining headroom (credits_cap − credits_used). A request that costs 150 credits is rejected at 9,900 used, because 9,900 + 150 > 10,000 even though you're under the cap.

What draws against the bucket

Three kinds of request are metered. Everything else — authentication, simulations, orders, positions, transactions, account management — is free. (Separately from the credit bucket, the unauthenticated /auth/* endpoints carry a small per-IP abuse throttle: a burst of 20 requests refilling at 10 per minute. Normal sign-up and login flows never see it; if tripped, the 429 carries a Retry-After header.)

Market data

Unauthenticated market-data traffic isn't metered; it can only reach the most recent completed session at 30-second resolution, so there's nothing to throttle. For authenticated accounts, each request costs in proportion to the data it returns:

Path What it does Credits
GET /market-data/strikes/{date} Lists the available strikes for a session 5
GET /market-data/historical/{date} Returns the index price series for a session 10
GET /market-data/option-chain-snapshots/{timestamp} Returns the full option chain at one moment 10
GET /market-data/option-chain-snapshots/{startTime}/{endTime} Returns option-chain snapshots over a time range 5 × snapshots

The range-snapshot endpoint is priced per snapshot returned, so a wide bulk fetch costs the same per data point as a single lookup — no bulk discount, and no penalty. See the market data API for what each endpoint returns and how to call it.

Strategy session drill-in

GET /strategies/{id}/results/days/{date} costs 10 credits — it replays that whole session through the engine on demand (which is also why it takes a few seconds). Clicking through a strategy's calendar is comfortably inside the budget; only a script hammering hundreds of session drill-ins in a loop will feel the bucket.

Backtest runs cost nothing

Running strategy results consumes no credits: the backtester is protected by a per-user cap of 3 concurrently-executing runs instead. Past the cap, POST /strategies/preview and POST /strategies/{id}/results/update return 429 {"error":"too_many_active_backtests"} — a concurrency signal, not a credit one. Retry with backoff (the web app uses 3 s doubling to 30 s); a slot frees as soon as one of your runs finishes, and saving a strategy never trips it (a save at the cap parks its run and starts it automatically later).

Handling 429

A credit 429 means the bucket would overflow, not that anything is wrong with the request itself. Back off and retry once enough credits have drained — credits leak back at about 417 per hour; for a small market-data request even a few seconds frees enough headroom. Metered responses also include a Retry-After header and an X-RateLimit-Used / X-RateLimit-Limit pair so an integration can track its own headroom request-to-request. (The concurrency 429 too_many_active_backtests above is different: it clears when one of your runs finishes, not on a timer.)