Limits and quotas
Two different mechanisms answer 429. Telling them apart is the whole page.
A rate limit and a quota both return 429, and they mean opposite things. One resets within
the minute; the other resets with the calendar month. Retrying is right for the first and useless
for the second — the reason field tells you which one you hit.
The two mechanisms
| Rate limit | Quota | |
|---|---|---|
reason | rate_limited | quota_exceeded |
| Counts | requests | work performed — audits run, strategies regenerated |
| Window | 60 seconds, fixed | the calendar month, UTC |
| Resets | within the minute | on the first of the month |
| Retrying helps | yes, after Retry-After | no |
Rate limit
Two counters run at once, and both must pass.
Per credential — 60 requests a minute by default. This is the number attached to your token; a plan can raise it.
Per project — 120 requests a minute, whatever the number of tokens. It exists so several credentials pointing at the same project cannot saturate it between them. An agency running one token per client is not affected; a single client running four tokens can be.
The response tells you where you stand
Every authenticated call carries the state of both counters, following the IETF RateLimit header fields:
RateLimit-Policy: "credential";q=60;w=60, "project";q=120;w=60
RateLimit: "credential";r=42;t=17, "project";r=98;t=17q is the quota, w the window in seconds, r what remains, t the seconds until the counter
resets. r never goes below zero — exhausted reads as r=0, not as a negative number.
The quota shown is yours. It is the limit carried by the credential you presented, not the ceiling of some other plan.
On a refusal
HTTP/2 429
Retry-After: 17
Content-Type: application/problem+json
{ "type": "…/reference/errors#rate_limited", "status": 429, "reason": "rate_limited" }Wait for Retry-After, do not guess. Guessing goes wrong in both directions: too short and you
walk straight back into the wall, too long and you give up throughput you were entitled to.
Retry-After is the header to branch on. The RateLimit fields are still an IETF draft; this one
has been a standard HTTP header from the start, and every client library already understands it.
The counter is shared across transports
The same credential is counted once, whether it calls through MCP or through /v1. Splitting your
traffic between the two buys you nothing — which is deliberate: the limit belongs to the credential,
not to the door it walks through.
Quota
A quota counts work performed, not requests. Reading is never quota'd; only the capabilities that start real work are.
Two of them carry a quota today:
audit.run.request— running an auditeditorial.strategy.regenerate.request— regenerating the editorial strategy
Both are marked writesOnInvoke in the catalogue: invoking them already starts the work and
consumes the quota. There is no dry run. Ask before calling one, not after.
The counter is per project and resets at the start of each calendar month, UTC. The ceiling comes from the plan.
Ceilings by plan
These are the ceilings the API and MCP enforce today, generated from the platform configuration —
not typed by hand. A 0 means the plan does not include that capability.
| Limit | Freemium | Solo | Small Business | Entreprise & Agence |
|---|---|---|---|---|
| Active projects projects | 1 | 1 | 5 | 30 |
| Audit runs started by an agent runs per calendar month, UTC | 2 | 5 | 20 | Unlimited |
| Strategy regenerations by an agent regenerations per calendar month, UTC | 2 | 5 | 10 | Unlimited |
| Topic-idea generations by an agent generations per calendar month, UTC | 2 | 5 | 10 | Unlimited |
| Content generations generations per calendar month, UTC | 0 | 5 | 20 | Unlimited |
| Content patches patches per calendar month, UTC | 0 | 30 | 120 | Unlimited |
| Chat conversation tokens tokens per calendar month, UTC | 0 | 0 | 400,000 | Unlimited |
There is no header for the remaining quota. Rate limits are observable before you hit them; quotas are not — you learn the ceiling by reaching it, or from the plan. If your integration needs to know in advance, say so: it is a gap we know about, not an oversight.
Telling them apart in code
Branch on reason, never on the status code alone:
if (res.status === 429) {
const problem = await res.json();
if (problem.reason === 'rate_limited') {
// Transient. Wait exactly what the server asked for.
await sleep(Number(res.headers.get('Retry-After')) * 1000);
return retry();
}
// quota_exceeded — retrying this month changes nothing.
return surfaceToUser(problem);
}Treating quota_exceeded as transient produces a client that retries until the first of the month.
That is the failure this page exists to prevent.