Developers menu

Rate limits

Every limit is per key or per account, never per page view. Read the remaining count from the answer's headers rather than hard-coding it.

ProductWhatLimitHeadersNotes
LIVELevels API120 requests a minute per keyX-RateLimit-Limit, X-RateLimit-Remaining; Retry-After on a 429A 304 (If-None-Match) is not counted. A live key answers from at most 3 client addresses an hour. History: the last 90 days; older with DATASET.
ORDERSOrders API and POSITIONS API20,000 calls a day on Orders API, 100,000 on Orders Team, per accountX-RateLimit-Limit, X-RateLimit-RemainingThe count resets at midnight ET. The Orders MCP server counts against the same budget.
LABLAB APICredits: a backtest or an optimize pass is 1 credit, an OPTIONS archive run 10noneDeveloping an idea is free but needs a positive balance. One idea holds the conveyor at a time. A failed job refunds its credit.
REPLAYArchive JSON and the session filesDistinct days per address: a visitor that walks many sessions in an hour is slowed429 with Retry-AfterEvery finished session is free to read one at a time. Bulk copies are the DATASET licence, which lifts the breadth limit.
REPLAYMCP server, free toolsget_gex_profile: 50 distinct days an hour and 150 in 24 hours per client addressa tool error that says when the window rollsDays already opened in the window keep answering. The other free tools have no per-address limit.
DATASETDATASET filesNo breadth limit for a licence holdernoneMirror the archive in one pass, then take each new session the evening it closes.
GEOGeo API and Geo MCP server20,000 calls a month on Geo API, 100,000 on Geo Team, REST and MCP togetherRetry-After on a 429; GET /v1/usage is freeThe month is the calendar month in UTC. A malformed call is not counted.

Backoff

On a 429 wait Retry-After seconds; on a 5xx or no answer, wait 1, 2, 4, 8 seconds (with random jitter) and give up after five tries.

async function call(url, opts, tries = 5) {
  for (let i = 0; ; i++) {
    const r = await fetch(url, opts);
    if (r.status !== 429 && r.status < 500) return r;
    if (i + 1 >= tries) return r;
    const after = Number(r.headers.get("Retry-After"));
    const wait = after > 0 ? after * 1000 : 2 ** i * 1000 * (0.5 + Math.random());
    await new Promise((ok) => setTimeout(ok, wait));
  }
}
import random, time, requests

def call(url, tries=5, **kw):
    for i in range(tries):
        r = requests.get(url, **kw)
        if r.status_code != 429 and r.status_code < 500:
            return r
        after = r.headers.get("Retry-After")
        time.sleep(float(after) if after else 2 ** i * (0.5 + random.random()))
    return r

Measurements of dealer positioning, never a forecast or a trade call. This page is built from FirmTape's public contract, the same files the OpenAPI document is built from; the tests hold them against the route code. Markdown: /docs/developers/limits.md.