Rate limits and quota
Your Pro subscription is held to two limits. They do different jobs, and hitting one tells you nothing about the other.
| Limit | Allowance | Period | Applies to |
|---|---|---|---|
| Monthly request quota | 10,000 | 30 days, renewing automatically | Your subscription — all keys share it |
| Rate limit | 60 | 60 seconds | Each key, separately |
Your monthly quota
A Pro subscription includes 10,000 requests a month. It renews automatically — there is nothing to click and no invoice to wait for — and an unused month does not roll over.
The quota belongs to your subscription, not to a key. Every key you hold draws down the same 10,000. Creating a second key gets you another credential for another environment; it does not get you another 10,000 requests.
Watch X-Quota-Remaining on any response to see what is left, and X-Quota-Reset for the moment it
renews. A quota period is 30 days rather than a calendar month, so the renewal date moves slightly
through the year — read the header rather than assuming the 1st.
When the quota is spent, requests are refused with a QUOTA_EXCEEDED 429 until it renews. Backing
off does not help, and neither does a new key.
The rate limit
Separately, each key may make 60 requests per 60 seconds — one per second, sustained. This is a burst control, not a second allowance: it stops a runaway loop from spending your whole month in an afternoon.
It is counted per key, which matters when you read the headers: X-RateLimit-Remaining describes the
key you authenticated with, not your account. Your quota is the account-wide number, and no
arrangement of keys changes it.
How the window works
The window is fixed, not a token bucket. There is no separate burst allowance: you may spend the whole minute's allowance in the first second and then be limited for the remaining 59, and that is expected behaviour rather than a misconfiguration.
We chose a 60-second window deliberately. Batch jobs burst by nature, and shaping traffic per-second would break that pattern for no benefit at the volumes this API serves.
Headers
Six headers appear on every successful response and on every 429 — one trio per limit:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | This key's ceiling for the current 60-second window |
X-RateLimit-Remaining | Requests left in the current window |
X-RateLimit-Reset | When the window resets, as a Unix timestamp |
X-Quota-Limit | Requests your subscription includes each month |
X-Quota-Remaining | Requests left this month, across all of your keys |
X-Quota-Reset | When the quota renews, as a Unix timestamp |
A 429 additionally carries Retry-After, in seconds.
Watch both trios. The rate limit headers say nothing about your quota: they will look perfectly healthy right up to the request that exhausts your month.
Read these rather than counting requests yourself. Your own counter cannot see a second process sharing the key, and it drifts from ours the moment either side restarts.
Handling a 429
Two different limits answer 429, so branch on error.code — the remedies have nothing in
common.
RATE_LIMITED — too fast this minute
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded for this API key.",
"requestId": "01JD8K2M4Q7X9V"
}
}Wait for Retry-After and retry. If you are running concurrent workers, add jitter — otherwise
every worker wakes at the same instant and you re-trigger the limit as a group.
QUOTA_EXCEEDED — out of requests this month
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly request quota exhausted for this subscription.",
"requestId": "01JD8K2M4Q7X9V"
}
}Retrying cannot succeed until the quota renews, and Retry-After points at that renewal — possibly
weeks away. Stop the job rather than retrying it, and check X-Quota-Reset for the exact moment.
Creating another key will not help: the quota is shared across all of them.
async function request(url, key, attempt = 0) {
const response = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
if (response.status !== 429 && response.status !== 503) {
return response;
}
// A spent monthly quota cannot be retried into succeeding — only the renewal
// clears it. Give up rather than burning attempts on a certainty.
if (response.status === 429) {
const { error } = await response.clone().json().catch(() => ({}));
if (error?.code === 'QUOTA_EXCEEDED') {
return response;
}
}
if (attempt >= 5) {
return response;
}
// Honour Retry-After when we send one; fall back to exponential backoff.
const retryAfter = Number(response.headers.get('Retry-After'));
const backoffMs = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 1000;
// Jitter, so parallel workers do not all wake at the same instant.
await new Promise((resolve) => setTimeout(resolve, backoffMs + Math.random() * 500));
return request(url, key, attempt + 1);
}A 429 is not a reason to open more connections or to create a second key. A burst of parallel retries spends the next window before your real traffic reaches it — and a second key buys you nothing at all against the quota, which every key you hold shares.
Staying within your limits
Every one of these buys you headroom against both limits — fewer requests is fewer requests. With a monthly quota they compound: trimming a hundred redundant calls a day is three thousand requests a month you keep.
Note that a conditional request answered 304 Not Modified still counts. It saves you bandwidth and
us the work, but the request was still made and still verified.
- Page with
limit=100. One request for 100 rows costs a hundredth of what a hundred single-row requests cost. The maximum page size is there to be used. - Cache what does not change. Boat specifications are editorial data that changes rarely. There is no reason to re-fetch the same model on every page view of your own site.
- Filter server-side.
?manufacturer=albin-marineis one request; fetching everything and filtering in your own code is hundreds. - Use
/v1/boats?manufacturer=slugrather than/v1/manufacturers/:slug/boatswhen you already know the slug and do not need the 404-on-unknown-manufacturer behaviour — it is the cheaper path on our side too.
Need more than 10,000 a month?
Email [email protected] with what you are building and the request volume you expect, and we will talk it through.
Whatever we agree, your existing keys keep working. You will not be issued replacements and you do not need to redeploy anything — the quota is attached to your subscription, not to the keys, so changing it changes nothing you are holding.
Sustained 429s are a signal
We alert internally on a key that is sustaining 429s, because in our experience that pattern is almost always a misconfigured integration — a retry loop without backoff, or a scheduled job that overlaps its own previous run — rather than deliberate abuse. If we see it on your key we may get in touch before you notice.