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.

LimitAllowancePeriodApplies to
Monthly request quota10,00030 days, renewing automaticallyYour subscription — all keys share it
Rate limit6060 secondsEach 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:

HeaderMeaning
X-RateLimit-LimitThis key's ceiling for the current 60-second window
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetWhen the window resets, as a Unix timestamp
X-Quota-LimitRequests your subscription includes each month
X-Quota-RemainingRequests left this month, across all of your keys
X-Quota-ResetWhen 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

JSON
{
  "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

JSON
{
  "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-marine is one request; fetching everything and filtering in your own code is hundreds.
  • Use /v1/boats?manufacturer=slug rather than /v1/manufacturers/:slug/boats when 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.