Quotas
Plan tiers, monthly unit allowances, and what happens when you exceed.
Units
Every API request that reaches a data endpoint costs at least 1 unit, and heavier endpoints cost more: see the weights table below. Quota resets at the start of each billing period.
Health and readiness checks (/healthz, /readyz) are free and never counted.
Odds: discovery 1× · spot board 5× · history page 10×. See the table and Odds history pages below.
Plans
| Plan | Price / mo | Units / mo | At the cap |
|---|---|---|---|
| Free | $0 | 5,000 | hard stop |
| Hobby | $30 | 25,000 | hard stop |
| Pro | $100 | 100,000 | hard stop |
| Scale | $500 | 1,000,000 | hard stop |
| Growth | $1,500 | 5,000,000 | hard stop |
| Enterprise | Custom | 10,000,000 baseline, contract-sized | Custom |
All paid plans have access to all competitions and all endpoints, including
/v1/analysis/*. The free tier covers every endpoint except /v1/analysis/*, which
returns 403 TIER_REQUIRES_PAID.
What 5,000 units actually buys you
Units are abstract: here's the translation in real calls. Pick the row that matches your workload.
| Workload | Weight per call | What 5,000 units gives you |
|---|---|---|
Raw lookups (person/team search, standings, contest detail, competitions, /v1/contests/{id}/odds/markets) | 1× | ≈ 5,000 lookups |
| Box scores, game logs, season stats, contest lists | 2× | ≈ 2,500 reads |
| Rosters and matchup boards | 3× | ≈ 1,600 pulls |
| Play-by-play (Statcast) | 5× | ≈ 1,000 games |
Odds spot board (/v1/contests/{id}/odds or /v1/persons/{id}/odds: all markets, latest tick) | 5× | ≈ 1,000 boards |
Odds history page (/v1/contests/{id}/odds/history or /v1/persons/{id}/odds/history: one market/entity, paged) | 10× | ≈ 500 pages |
Analysis cards (/analysis/*) | 10× | ≈ 500 cards (paid tier) |
The exact weight for each endpoint is published in the OpenAPI spec as
x-statshawk-weight and shown on each REST reference page.
On a paid plan, 5,000 units covers about 500 analysis cards. Free accounts can use every non-analysis tool — including odds discovery, spot boards, and history — but cannot call analysis endpoints.
Odds history pages
GET /v1/contests/{id}/odds/history and GET /v1/persons/{id}/odds/history return one
market/entity per request, newest-first. Cost is 10 units per page, not per tick:
interval only changes how ticks are bucketed. Each extra page costs another 10 units.
History requires markets and/or measure. A request with neither is rejected. Person
history without a contest also needs that filter plus a time window (default 24 hours).
| Param | Default | Max | Meaning |
|---|---|---|---|
markets and/or measure | required | — | Scope the page to one market/entity |
limit | 50 | 100 | Rows per page. Accepted range is 1 to 100; 0 or anything above 100 is rejected with 400, not clamped |
cursor | — | — | Prefer this. Pass data.next_cursor back unchanged for the older page. Opaque token; tie-safe on raw pages, bucket-aligned on interval pages |
before | — | — | Exclusive timestamp window, not a pager. Same-timestamp leftovers can be skipped |
since | — | — | Inclusive lower bound on observed_at |
interval | auto | — | raw, 15m, 1h, or auto. auto coarsens for longer windows |
Paging fields live on data (OddsHistoryPage), not on meta (ResponseMeta is
only request_id, fetched_at, cache, version, optional source). When the tape is
longer than one page, data.truncated is true and data.next_cursor is the token for
?cursor=. Treat the token as opaque: the current forms are raw|{observed_at}|{id} for
raw pages and bucket|{start} for interval pages, and the wire format may change.
data.next_before may also appear as a timestamp window; do not use it as the pager when
next_cursor is present.
Discovery (/v1/contests/{id}/odds/markets) stays 1×. The current spot board
(/v1/contests/{id}/odds or /v1/persons/{id}/odds) is 5×: all markets, latest tick,
one entity. Odds stay Free-accessible; usage is the gate. Search pagination (limit /
offset) is on Pagination.
Monitoring usage
Your current-period usage is visible on the Billing page. The response
headers on metered customer endpoints also carry your real-time quota state. See
Rate limits for the header names. (Unmetered routes,
/v1/ingest/* and /v1/assets/*, carry no quota headers.)
Exceeding your quota
Hitting the monthly cap is a hard stop on every metered route for Free through
Growth: further requests return 429 Too Many Requests with error.code: "QUOTA_EXCEEDED"
until the period resets or you upgrade. The X-Account-Quota-Reset header carries the
period-end timestamp. Enterprise cap handling is contract-defined.
There is no metered overage billing. You are never charged past your plan price.
Need more units? Upgrade from the Billing page, or compare allowances on Pricing. The new limit applies immediately.
See Errors for the full error shape and retry guidance.