Working with the API

Rate limits and quotas

Two different limits, measured differently. A rate limit is about the next second; a quota is about the month. Both are visible before you hit them.

Updated September 24, 2026

3 min read

The headers, on every response

These come back on successful responses too, not only on a 429. A client that only learns its limit by being refused cannot avoid being refused.

http
RateLimit-Policy: "plan";q=20;w=2, "product";q=20;w=2, "key";q=20;w=2
RateLimit: "key";r=19;t=1
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 19
X-RateLimit-Reset: 1
  • RateLimit-Policy lists every bucket this request passed through. q is how many requests the bucket holds when full, and w is how many seconds it takes to refill from empty. q=20;w=2 means 20 at once, refilled at 10 a second.
  • RateLimit names the bucket closest to empty. r is what is left in it and t is the seconds until it is full again.
  • The bucket names depend on the credential: key for a project key, agent for a connected AI tool, dashboard for a signed-in person. The numbers come from your plan.

On a 429 there is also Retry-After, in seconds. Honour it — it is the real number, and guessing is how a thundering herd gets built.

Per second

A token bucket, so a short burst above your steady rate is fine as long as the average holds. "30 requests a second, burst 60" means exactly that: you may spend 60 at once, and it refills at 30 a second.

Testing the limit one request at a time will not hit it. Requests sent one after another wait for each answer, often 100 ms or more, so they arrive at about the refill rate and the bucket never empties. r then stays the same from one response to the next. That is the limiter working, not missing. To see a 429, send a burst in parallel, larger than q.

A parallel burst of 60 — expect some 429s with Retry-After

bash
CMSKITE_KEY=csk_live_... node -e '
const h = { authorization: "Bearer " + process.env.CMSKITE_KEY }
const url = "https://api.cmskite.com/v1/blog/posts?limit=1"
Promise.all(Array.from({ length: 60 }, () => fetch(url, { headers: h }))).then((rs) => {
  const c = {}
  for (const r of rs) c[r.status] = (c[r.status] ?? 0) + 1
  const hit = rs.find((r) => r.status === 429)
  console.log(c, hit ? { retryAfter: hit.headers.get("retry-after"), policy: hit.headers.get("ratelimit") } : "no 429")
})'

With q=20;w=2 expect roughly 20 to 40 answers of 200 (the bucket, plus what refills while the burst is in flight) and the rest 429.

Your steady rate and burst come from your plan, and the live figures are on the plans page. GET /v1/billing returns the exact numbers your key is held to.

Several buckets apply at once — your plan, the product, the individual key, the route — and all of them have to pass. Which one refused you is in details.policy on the 429.

Per month

Your plan includes a number of API requests per billing period. Reads served from your own cache do not count, which is most of the reason a sixty-second revalidate is worth more than an upgrade.

Free, Starter and Team include a fixed number of requests a month; on Custom you choose the tier. Past the limit a request is refused with 429 QUOTA_EXCEEDED — it is never silently billed.

Usage never slows a request down. Usage is recorded after the response has been flushed to the socket, so measuring it costs you no latency. Your dashboard figures come from rollups, not from raw events.

When the platform is at capacity

Above a concurrency ceiling an instance answers 503 SERVICE_UNAVAILABLE with a short Retry-After instead of queueing. That is deliberate. A refused request costs nothing and is survivable; a queued one is how an instance takes itself down, and everybody waiting behind it with it.

Treat a 503 as "try again in a moment", not as an outage. It means the platform chose to stay up.

Staying under

  • Cache. A published post changes rarely and is read constantly.
  • Ask for what you need: ?limit=100 once beats twenty-five requests of four.
  • Use include and the relations already on a listing rather than a request per row. A listing returns its categories, tags and authors with it.
  • Read RateLimit-Remaining and slow down before it reaches zero.
  • Back off exponentially with jitter when you are refused, and honour Retry-After when it is present.