Get StartedDocs

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: 2xx responses, plus 4xx responses derived from processing the request itself (not-understood, not-supported, validation-failed and client-closed-request when you disconnect mid-answer).
  • Does not count: 429 rate-limit responses; 402 responses (quota-exceeded, the subscription-* family and channel-not-entitled); auth-* failures and account-disabled; requests turned away before any answer work (route-not-found, unsupported-version, payload-too-large); and any 5xx server-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_feedback keep 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-Remaining over 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.