Rate Limits & Quotas
The API enforces two independent limits, documented in separate sections below.
| Limit | Window | Status when exceeded | Header reporting it |
|---|---|---|---|
| Rate limit | Per second | 429 |
X-RateLimit-Limit, X-RateLimit-Remaining |
| Period quota | Per billing period | 402 |
X-Quota-Limit, X-Quota-Remaining, X-Quota-Reset |
The rate limit protects the API from sudden spikes; the period
quota tracks contracted usage. The two are independent: a tight loop can
trigger 429 long before exhausting your quota, and a steady low rate
can exhaust the quota without ever tripping the rate limiter.
Both limits are account-wide: REST requests and MCP tool calls draw from the same rate limit and the same quota. See MCP tool calls for the MCP-specific differences.
Rate limit
The rate limiter uses a token bucket algorithm: each request consumes one token, and tokens refill at a steady rate. The default is 5 tokens per second. Some accounts have higher limits; the dashboard reports your actual values.
Responses that pass the rate limiter include:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Rate limit capacity (the maximum tokens the bucket holds). |
X-RateLimit-Remaining |
Tokens currently available in the bucket. |
Exceeding the limit returns 429 with a Retry-After header, in seconds
(typically a small integer):
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 1
{
"type": "https://api.statmuse.com/errors/rate-limited",
"status": 429,
"title": "Too Many Requests",
"detail": "You have exceeded the allowed request rate."
}
A simple bounded retry is usually enough:
async function askWithRetry(input: string): Promise<unknown> {
const url = new URL("https://api.statmuse.com/sports/nba/v1/ask");
url.searchParams.set("input", input);
for (let attempt = 0; attempt < 2; attempt++) {
const res = await fetch(url, {
headers: { "x-statmuse-api-key": process.env.STATMUSE_API_KEY! },
});
if (res.status !== 429) return res.json();
const retryAfter = Number(res.headers.get("Retry-After") ?? "1");
await new Promise((r) => setTimeout(r, retryAfter * 1000));
}
throw new Error("Rate limited after retry");
}
If 429 responses are persistent, reduce client-side concurrency
(queueing, fewer parallel workers) rather than retrying more
aggressively.
Period quota
Each account has a request quota tied to its billing period. Period
boundaries are set per-account and are not necessarily aligned to the
calendar month, so treat X-Quota-Reset as the source of truth.
Every answered request (2xx responses and the 4xx rejections that
count against your quota) carries:
| Header | Example | Meaning |
|---|---|---|
X-Quota-Limit |
10000 |
Total requests allowed in the current period. |
X-Quota-Remaining |
9842 |
Requests remaining before the period quota is hit. |
X-Quota-Reset |
2026-06-01T00:00:00Z |
ISO 8601 timestamp when the period resets. |
Requests refused at an access gate (401/403, 429, 402) don’t
carry these headers; the 402 responses report the same state in their
body instead.
When the quota is exhausted further requests return 402:
HTTP/1.1 402 Payment Required
Content-Type: application/problem+json
Retry-After: <seconds-until-reset>
{
"type": "https://api.statmuse.com/errors/quota-exceeded",
"status": 402,
"title": "Quota Exceeded",
"detail": "The quota for this account has been reached for the current billing period.",
"quotaLimit": 10000,
"quotaUsed": 10000,
"resetAt": "2026-06-01T00:00:00Z"
}
Retry-After is the number of seconds until your period resets; for a
fresh quota-exceeded this is typically days, not seconds. Do not retry
in a tight loop. Queue work for after the reset or surface the failure
to the end user.
What counts against your quota
The quota counts billable responses:
- Counts:
2xxresponses, plus4xxresponses derived from processing the request itself (not-understood,not-supported,validation-failedandclient-closed-requestwhen you disconnect mid-answer). - Does not count:
429rate-limit responses;402responses (quota-exceeded, thesubscription-*family andchannel-not-entitled);auth-*failures andaccount-disabled; requests turned away before any answer work (route-not-found,unsupported-version,payload-too-large); and any5xxserver-side error.
In short, any request that reaches the API and produces an answer, including a 4xx response derived from your input, counts as one request. A request rejected at the edge before we do that work does not.
MCP tool calls
There is no separate MCP quota: a billable tool call counts one request against the same period quota, and every call consumes a rate-limiter token, so REST and MCP usage combine. Heavy REST traffic can rate-limit your agent, and vice versa. Everything above applies, with two MCP-specific differences:
- Billability follows the same principle. A tool call that produces
an answer counts, including a tool error caused by your own input,
such as an unanswerable query. Tool failures on our side and calls
refused at a gate don’t count, and protocol housekeeping (connecting,
listing tools) is never billed; only tool calls are. The one tool
that never bills is
submit_feedback: reporting a problem with the tools is free. - An exhausted quota doesn’t touch the connection. Connecting,
listing tools and
submit_feedbackkeep working over quota. Any other tool call instead returns an MCP tool error carrying the quota message (the numbers, the reset date, and where to upgrade) so your agent sees it directly and can relay it to you.
The X-Quota-* headers ride the over-quota tool-call response too,
with X-Quota-Remaining: 0, but MCP clients don’t surface HTTP
headers, so the dashboard is the
practical place to watch usage.
Recommendations
- Read both header sets on every response. Logging
X-Quota-Remainingover time surfaces a trend toward the limit days before it is hit. - Do not poll for the reset. Read
X-Quota-Reset, schedule a wake-up and stop calling until that time. - Cache identical questions. When end users repeat the same query (e.g. “Lakers schedule”), serve repeats from your own cache.