Rate limits
Limits apply per account and are shared by every API key on it. They exist to keep latency predictable for all customers; bulk history has its own path through S3, which has no request limit.
REST limits by plan
| Plan | Sustained | Burst | Concurrent requests |
|---|---|---|---|
Test key (fdc_test_) | 5 req/s | 5 | 2 |
| Researcher | 10 req/s | 20 | 4 |
| Desk | 50 req/s | 100 | 16 |
| Enterprise | custom | custom | custom |
The limiter is a token bucket. Each request takes one token; tokens refill at the sustained rate up to the burst size. A Researcher account that has been idle can send 20 requests at once, then 10 per second after that. Test keys have their own 5 req/s cap and do not count toward your plan's limit. GET /v1/usage is not counted.
Page sizes are the same on every plan: up to 10,000 rows per JSON page and 1,000,000 per CSV page. Parquet responses are not paginated and cover up to 31 days per request.
Concurrent requests are those still streaming a response. Requests above the concurrency limit are rejected with 429 even if tokens remain, since a large Parquet page can take several seconds to transfer.
Response headers
Every response, including errors, carries the current state of your bucket.
| Header | Example | Meaning |
|---|---|---|
X-RateLimit-Limit | 50 | Sustained requests per second for your plan. |
X-RateLimit-Burst | 100 | Bucket size. |
X-RateLimit-Remaining | 37 | Tokens left after this request. |
X-RateLimit-Reset | 1.26 | Seconds until the bucket is full again. |
X-RateLimit-Concurrent | 3/16 | Requests in flight, including this one, out of your limit. |
Retry-After | 0.4 | On 429 only: seconds to wait before the next request will be accepted. May be fractional. |
Handling 429
A rejected request returns status 429 with error code rate_limited and does not consume a token. Wait at least Retry-After seconds before retrying. If you run several workers, back off with jitter so they do not retry in lockstep.
{
"error": {
"status": 429,
"code": "rate_limited",
"message": "50 req/s exceeded for this account. Retry after 0.4 s.",
"request_id": "req_01JB7QR8N6P4Q2R0S8T6V4W2X0"
}
}import random, time
import httpx
def get(client, url, params, max_tries=6):
delay = 0.25
for attempt in range(max_tries):
r = client.get(url, params=params)
if r.status_code == 429:
wait = float(r.headers.get("Retry-After", delay))
elif r.status_code >= 500:
wait = delay
else:
r.raise_for_status()
return r
time.sleep(wait + random.uniform(0, delay))
delay = min(delay * 2, 8)
r.raise_for_status()The Python SDK does this for you with retries=3 by default. To stay under the limit without hitting 429 at all, pace requests by X-RateLimit-Remaining and X-RateLimit-Reset.
An account that keeps sending while rate limited (more than 1,000 rejected requests in a minute) gets a Retry-After of 60 seconds.
WebSocket limits
Live streaming is available on Desk and Enterprise plans.
| Limit | Test key | Desk | Enterprise |
|---|---|---|---|
| Concurrent connections per account | 1 | 10 | custom, default 50 |
| Subscriptions per connection | 10 | 250 | 500 |
| Subscriptions per account | 10 | 1,000 | custom |
| Client operations per second, per connection | 5 | 20 | 50 |
| New connections per minute | 5 | 30 | 120 |
Replay buffer for resume_from | 1 h | 24 h | 72 h |
A subscription is one channel, one venue and one symbol. A list of symbols counts once per symbol; a "*" subscription on a low-volume channel counts once. Going over a subscription limit returns an error with code subscription_limit for that request only. Going over the connection or operation limits closes the connection with code 4029. A resume_from older than the replay buffer closes it with code 4010.
A test key stream replays the sample day (2026-09-15 UTC) at recorded speed.
Bulk history: use S3
REST is designed for targeted queries: an hour of L3 around an event, a month of funding, the latest instruments. Pulling large ranges over REST is slow because of page sizes and the 31-day cap on Parquet requests, and it competes with your own queries for tokens.
| Task | Best path |
|---|---|
| One symbol, minutes to hours of any dataset | REST or the SDK |
| Funding, open interest, candles over months | REST or the SDK |
| Book data (L2, L3, L4) beyond a few days | S3 |
| Many symbols, full days, research datasets | S3 |
| Live data with history up to now | SDK with live=True |
As a reference point, one month of Hyperliquid BTC-PERP L2 is about 6.4 billion rows. As CSV at 1,000,000 rows per page, that is about 6,400 requests, roughly 11 minutes of Researcher rate limit before any transfer time; as Parquet it is one 31-day request of about 54 GB. In S3 it is 30 files you can read in parallel. Reads from S3 do not count against REST limits.
If your workload needs higher limits, write to chris@financedatacloud.com or see pricing for Enterprise.