---
title: Rate limits
description: Three separate budgets, counted per key, with headers that let you slow down before you are refused.
---

## The budgets

| Budget | Per minute | Covers |
|---|---|---|
| `read` | 600 | Every `GET` |
| `write` | 60 | Every write except votes |
| `vote` | 30 | `POST /v1/kb/articles/{slug}/vote` |

A request spends **one** budget. Reading all day never uses up your allowance
to vote, and a site doing nothing but reading is never refused a write it never
attempted.

The numbers are sized against real use: a static build fetching every article,
category and author is a few dozen calls; a thousand-article import takes about
seventeen minutes at 60 writes a minute, which is the right speed for something
rewriting a customer's content.

## Counted per key, not per address

Server-to-server traffic arrives from one address, often one NAT or serverless
region shared by many projects. An address budget would either throttle a whole
platform down to one project's share, or be set so high it protects nothing.

**Publishable keys are the exception**: they are counted per key **and** per
address, because they live in a browser where anyone can read them. One visitor
hammering your help centre is limited without limiting every other visitor
holding the same key.

> **WARNING: Do not use a publishable key from a server**
>
> Every request from your server shares one address budget. It looks like a much
> lower limit than the table says, and the cause is invisible from the response.
> Use a secret key server-side.

## The headers

Every response carries, for the budget it spent:

```
X-RateLimit-Limit-read: 600
X-RateLimit-Remaining-read: 587
X-RateLimit-Reset-read: 43
```

`Remaining` is what this exists for. Read it and slow down **before** you are
refused. That is cheaper for you than a 429, and cheaper for everyone else too.

A `429` additionally carries:

```
Retry-After: 51
```

## Handling a 429

```json
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Too many requests on this API key. The limit is 30 per minute on this endpoint. Wait 51 seconds and try again…",
    "request_id": "8abf0d3d-…"
  }
}
```

Wait `Retry-After` seconds, then retry.

> **INFO: Retry-After is when the refusal lifts, not when the window rolls**
>
> They are different instants: the block is set partway through a window and runs
> its own duration from there. We send the later one on purpose, so a client that
> obeys the header is never refused for doing as it was told.
>
> While you are being refused, requests are **not counted**, so hammering
> neither helps nor hurts you. It just wastes your own time.

If you are backfilling, spread the writes out. If you are serving a site, cache
the reads: content changes when somebody edits it, not on every page view.

## Shared limits across instances

The counters are shared, so the numbers above are the numbers whether you run
one instance or twenty. Scaling your frontend horizontally does not multiply
your budget.

## Asking for more

The limits are per key, so splitting a workload across keys splits the budget
rather than multiplying it. One key per project is the intended shape, and
minting extra keys to route around a limit will not work the way it looks like
it should. If a legitimate workload does not fit, tell us what it is.