Getting started · Plans, quota & rate limits
How much, how fast, how far back
Each plan is three numbers: a request quota, a per-minute rate and a history window. Here is how each one is measured, what the response headers tell you, and exactly what happens when you reach an edge.
The plans side by side
| Plan | Price / month | Quota | Requests / min | History | Keys | WebSocket | Premium endpoints |
|---|---|---|---|---|---|---|---|
| Free | $0 | 1,000 requests / day | 10 | Last 7 days | 1 API key | Not included | Not included |
| Starter | $49 | 250,000 requests / month | 120 | Last 30 days | 2 API keys | Not included | Not included |
| Pro | $199 | 1,000,000 requests / month | 600 | Last 6 months | 5 API keys | Not included | Yes |
| Business | $999 | 10,000,000 requests / month | 6,000 | Full history | Unlimited API keys | Full + replay | Yes |
Two limits, counted separately
Both apply to every authenticated /v2 call and both belong to the account: all of its keys draw from the same two pools.
The quota
How many, per period
- Paid plans count by UTC calendar month; Free counts by UTC day.
- Reported on every response in three RateLimit headers.
- Once it is used up: 429quota_exceeded, and Retry-After tells you how many seconds remain until the reset.
The rate
How many, per minute
- Fixed one-minute windows that protect against bursts.
- No headers of its own — you only notice it when you cross it.
- When you cross it: 429rate_limited, with Retry-After pointing at the next minute. Not counted against the quota.
The quota and its headers
On Starter, Pro and Business the quota covers a UTC calendar month and resets on the 1st at 00:00 UTC. On Free it covers a UTC day and resets each night at 00:00 UTC. It is not a rolling window: a request made at 23:59 UTC on the last day of the month still uses the old month's allowance.
Every response tells you where you stand, so a client can slow itself down long before it hits zero. When the quota is gone the API answers 429 quota_exceeded until the reset; the error message states which period ran out and when it resets, and Retry-After holds the seconds left. Retrying sooner only produces more 429s.
HTTP/1.1 200 OK
Content-Type: application/json
RateLimit-Limit: 250000
RateLimit-Remaining: 187412
RateLimit-Reset: 1036800| Header | Holds |
|---|---|
| RateLimit-Limit | Your plan's quota for the current period. |
| RateLimit-Remaining | Requests still available in this period. |
| RateLimit-Reset | Seconds until the period ends and the quota refills. |
The per-minute rate
The rate is a burst guard, counted in fixed windows that start on the minute. Each plan's figure is in the table above.
- accepted
- 429 rate_limited
- your plan's limit
Minute 1
under the limit
Minute 2
limit hit · rest refused
Minute 3
count starts again
Crossing the rate returns 429 rate_limited. Retry-After gives the seconds until the next window opens, which is never more than a minute. These refusals are not charged to your quota, so waiting and retrying costs nothing.
The two 429s need opposite reactions — a short wait for one, a full stop for the other — so always read error.code rather than the status alone. The official SDKs already retry rate_limited for you.
const API = "https://api.datasportsapi.com";
const headers = { Authorization: `Bearer ${process.env.DATASPORTSAPI_API_KEY}` };
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
export async function get(path: string, attempts = 3) {
for (let i = 1; ; i++) {
const res = await fetch(API + path, { headers });
if (res.status !== 429) return res.json();
const { error } = await res.json();
// A spent quota will not come back for hours or days: stop and alert.
if (error.code === "quota_exceeded" || i === attempts) throw new Error(error.message);
// rate_limited: Retry-After points at the start of the next minute.
await sleep(Number(res.headers.get("Retry-After") ?? 1) * 1000);
}
}How far back you can read
A plan's history window is measured back from the start of today in UTC. Upcoming and live data are never limited.
- Free7 days
- Starter30 days
- Pro6 months
- BusinessFull history
Anything that starts before the window is refused or trimmed, depending on what you asked for:
- A single old record — a fixture that kicked off earlier, the day list for an earlier date, or a league season that is already over (its standings, rounds, top players and top teams, or team and player season statistics) — answers
403 history_not_in_plan. - A list that reaches back — a team's or player's fixtures, the fixtures of a round — leaves the older fixtures out and reports the cut-off in
meta.historyFrom.
The refusal carries historyFrom, the first day your plan can read, so you can clamp a date picker to it. These refusals count against your quota like any other answered request.
{
"error": {
"code": "history_not_in_plan",
"message": "this fixture is older than your plan's history",
"historyFrom": "2026-11-03"
}
}What counts as a request
Requests are requests: no credits. Every plan has all 24 sports and the core match data.
Counts as one
- Every answered /v2 call, whichever endpoint it hits.
- Each stream ticket —
POST /v2/stream/ticketis an ordinary call. - Every refresh of an embedded widget, in every visitor's browser.
- Calls made through the SDKs and the dsa CLI.
- Refusals for history_not_in_plan.
Never charged
- 401unauthorized — no valid key.
- 403endpoint_not_in_plan — the endpoint is above your plan.
- 403ip_not_allowed — outside your allowlist.
- 429rate_limited — over the per-minute rate.
- Frames on an open WebSocket and webhook deliveries — they are pushes, not /v2 calls.