APIHorseRacing

Documentation / Guides

Rate limits and quotas

Per minute, per month, and the headers that tell you.

Two limits, both per key, both visible on every response. Neither is a surprise if you read the headers.

The limits

PlanPer minutePer month
Free key6015,000
Live180250,000
Archive90150,000
Complete180400,000
Analyst2401,000,000

Archive's rate is lower than Live's on purpose. It is research at research pace: nine years of history and no live window, priced for somebody walking the archive rather than serving a page.

The headers

X-RateLimit-Limit:     180
X-RateLimit-Remaining: 174
X-RateLimit-Reset:     1757423460
Retry-After:           12          # only on a 429

Read X-RateLimit-Remaining on successful responses and slow down before you hit zero, rather than sprinting into a 429 and reacting. It is on every response and costs nothing to check.

The two 429s are different

CodeMeansDo
rate_limitedToo fast this minute. Sleep for Retry-After and continue. Genuinely transient.
quota_exceededMonthly allowance gone. Stop. It resets on the first. Retrying achieves nothing.

Branch on the code rather than the status. A client that retries both on a backoff spends the rest of the month asking a question it already has the answer to.

Pacing, rather than backing off

A fixed gap between requests is simpler and better behaved than a retry loop.

$perMinute = 180;
$gap = (int) (60_000_000 / $perMinute * 1.1);   // 10% headroom

foreach ($dates as $date) {
    fetchDay($date);
    usleep($gap);
}

Ten percent of headroom costs you almost nothing and means clock drift between your server and ours never trips the limit.

Watching the month

GET /v1/account/usage returns requests, errors and every refusal, by day or by endpoint, and does not count against your own quota. Call it daily and warn yourself at whatever threshold you are comfortable with.

by=endpoint is also the fastest way to find a wasteful loop. A single endpoint dominating a month is usually a poller that should be a webhook.

Things that do not cost you a request

  • Webhook deliveries. Zero, always. Polling is the largest source of wasted requests we see and charging for the alternative would be perverse.
  • Caching. A settled race never changes. Re-fetching one is a request you chose to spend.
  • 4xx responses. They count toward the rate but we would rather you fixed the cause than tuned around it.

If a limit is genuinely wrong for you

Tell us what you are building. A rate that blocks a legitimate use is a badly chosen number rather than a policy, and it is easier to change than you might expect.