Saltar al contenido

Docs / Límites de uso

Rate limits

Per-token read and write budgets, the pre-auth per-IP bucket, and exactly what a 429 looks like.

Rate limits here are anti-abuse guardrails on the control plane, not usage quotas on your work. Nothing on this page meters how many issues the agent triages or how many CI minutes you burn, those are covered by your seat, and your only variable cost is your own token bill (see BYOK).

What is limited is how fast a client may call the API.

The buckets

Three sliding windows, all one minute wide.

Bucket Keyed on Budget
Reads Your token or session 60 / minute
Writes, per conversation Your token or session, and the conversation 10 / minute
Writes, per token Your token or session 60 / minute
Pre-auth Caller IP 300 / minute

The per-IP bucket is applied before your credential is resolved. That is deliberate: it bounds credential probing and keeps an unauthenticated flood from reaching the database at all.

Read and write are separate budgets, not a shared pool. Spending your writes does not stop you polling.

Why writes have two numbers

A write is charged twice: once to the conversation it belongs to, once to the token overall. The narrow bucket is checked first, so one busy conversation cannot drain the budget of the others beside it.

Naming a conversation is how you get your own fair share. Today the only surface that does is MCP: pass conversation_ref as an argument and that conversation gets its own 10 a minute, up to the token ceiling of 60. Everything else shares one anonymous conversation, so its effective limit is the 10: every REST write, and any MCP call that does not carry the argument.

Because two buckets can bind, the RateLimit-* headers report whichever one runs out first: seeing 10 on one write and 60 on the next is correct, not a bug.

What counts as a write

By default, the class is the HTTP method: POST, PUT, PATCH and DELETE are writes, everything else is a read.

MCP is the exception. Every MCP call is a single POST /v1/mcp, so charging them all to the 10-per-minute write bucket would rate-limit read verbs like box list almost immediately. MCP requests are classified by the verb's scope instead: a read verb spends from the read budget.

Endpoints with their own limit

Endpoint Limit
GET /v1/public/oss/:owner/:repo/stats 60 / minute per IP
GET | POST /unsubscribe 30 / minute per IP

POST /v1/runners/hardening is not rate-limited. It is our own runner code reporting once per install, and several boxes can share one egress IP behind NAT, and a per-IP cap there would refuse a legitimate box rather than a prober. The bearer is checked before any lookup, so it is not a flood surface either.

Watching a run without spending your whole read budget

A 60-per-minute read budget and a once-a-second poll are the same number. Watch one agent run at 1 Hz and you have spent 100% of your reads on it, with nothing left for anything else, and a fleet rollout (box update) runs for minutes to hours.

So the run tail takes a long poll. Send wait_ms and the request is held open until the first new event lands, up to 25 seconds, and answers the instant one does:

curl "https://api.developerz.ai/v1/tasks/$TASK/events?since_seq=$CURSOR&wait_ms=25000" \
  -H "Authorization: Bearer dev_pat_…"
{ "run_id": "run_…", "events": [], "next_since_seq": 412 }

Over MCP it is the same knob on the same capability:

{ "action": "run_tail", "run_id": "run_…", "since_seq": 412, "wait_ms": 25000 }
Watching one run for an hour Requests spent
polling every second 3600, and every minute of it is your entire read budget
wait_ms=25000 on an idle run about 144, roughly 4% of the budget

Five rules worth knowing before you build on it:

  • An empty page after a wait is not an error. It means nothing happened in that window. Re-send with the same cursor. next_since_seq echoes your own cursor back for exactly this reason, so a caught-up watcher never rewinds to the head of the run.
  • Omit it, or send 0, and nothing changes. The call answers immediately, exactly as it did before the knob existed.
  • A refusal is never held. An unknown task, or a run that is not yours, is answered straight away rather than after the wait. The shape depends on the transport: over REST it is a 404 with a problem+json body, and over MCP it is a tool result flagged isError naming the run, because a JSON-RPC result carries no HTTP status.
  • A run that already ended settles the wait too. Once its final event has landed nothing can arrive after your cursor, so the call answers an empty page at once instead of holding the window open. That is your cue to stop: read GET /v1/tasks/$TASK (or MCP task run_status) and you will see the run finished.
  • Above 25000 is refused, not clamped. A long poll writes nothing until it answers, so it cannot survive the 30-second idle timeouts common to proxies and HTTP clients the way a streaming connection can.

Why a long poll and not a bigger budget

The other way to fix this would be a third bucket just for watching. We did not build one, on purpose: it would hand you more requests to spend without making any of them carry more, we would still answer 60 empty reads a minute per watcher, and you would have another budget to reason about. Fewer requests that each carry an answer is the better trade for both sides. If you are an agent, it is not close: every poll is a tool call whose result lands in your context, so 60 empty answers a minute is a context bill long before it is a rate-limit one.

It is a POST, and it always will be

There is no server-push transport on the MCP surface. Every MCP call is one JSON-RPC POST /v1/mcp and a tool result is one message, so a verb cannot hold a stream open. wait_ms is the whole answer there, and it is not a stopgap for a subscription that is coming later.

REST has both shapes, because they are two different capabilities rather than one answer in two envelopes. GET /v1/tasks/:id/events is the page you poll; GET /v1/fleet/runs/:runId/events is the Server-Sent Events stream you subscribe to, resumable with Last-Event-ID. Use the stream if you can hold a connection open. Use wait_ms if you cannot, which includes every agent talking to us over MCP.

Every response tells you what is left

You do not have to count your own calls, and you should not have to get throttled to discover the cap. Every /v1 response produced behind the auth guard carries three headers:

HTTP/1.1 200 OK
RateLimit-Limit: 60
RateLimit-Remaining: 58
RateLimit-Reset: 41
Header Means
RateLimit-Limit the budget of the bucket this request was charged against
RateLimit-Remaining what is left in it after this request
RateLimit-Reset seconds until a slot frees, never a timestamp

Two things follow from "the bucket this request was charged against":

  • A read and a write report different limits, because they are different budgets. Seeing 60 on a GET and 10 on the POST right after it is correct.
  • On POST /v1/mcp the numbers follow the verb, not the method, exactly the way the bucket does. tools/list reports the read budget.

A request that fails to authenticate reports the pre-auth per-IP bucket instead, because that is the only one it spent.

Two /v1 endpoints are mounted ahead of the guard and send no RateLimit-* headers at all: GET /v1/openapi.json and the public OSS stats badge. Both are publicly cacheable, so a per-caller counter sitting behind a shared cache would report a wrong number to everyone reading it. Treat their absence as the contract, not as a failed request.

What a 429 looks like

Errors are RFC 9457 problem details:

HTTP/1.1 429 Too Many Requests
content-type: application/problem+json
retry-after: 37
RateLimit-Limit: 60
RateLimit-Remaining: 0
RateLimit-Reset: 37
{
  "type": "https://api.developerz.ai/problems/rate-limit",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "rate limit exceeded",
  "instance": "/v1/repos",
  "extensions": { "retry_after": 37 }
}

Back off for retry-after seconds. The same number is repeated inside extensions.retry_after so a client that only parses the body still has it, and in RateLimit-Reset, because for a sliding window they are the same instant.

Two places the headers are not sent

  • GET /v1/openapi.json and the public OSS stats badge. Both are cacheable by anyone, and a per-caller counter stored in a shared cache is a wrong number served to everybody behind it until the entry expires. They still have their own per-IP limits, listed above.
  • Anything spelled X-RateLimit-*. An earlier version of the API reference documented X-RateLimit-* headers and a 1000-per-minute per-account limit. Neither ever existed. The headers above are the real ones and use the names from the IETF RateLimit header fields draft, with no X- prefix.

Limits you may feel that are not rate limits

Two other ceilings are easy to mistake for throttling:

  • Concurrency. One paid seat admits one concurrent AI-dev runner. Work above that waits in the queue rather than being rejected. See billing.
  • Spend caps. A monthly USD cap bounds the orchestration we do for you, not your model bill, and pauses dispatch when it is reached. Your inference spend is billed by your own provider on your own key; cap it in their console. See billing.

Both are described on the billing page. Neither produces a 429.