Rate limits
The Ciresk dev API limits how many requests each identity can make in a minute, and how many failed sign-ins one network address can cause. The limits exist to stop floods and runaway loops. Normal use by people, scripts and agents stays well below them. This page lists the limits, shows the exact answer you get when you go over one, and explains how ciresk dev and its MCP tools report it.
Limits per identity
Each identity has its own counter for each class of route. A person (user:), an agent (agent:) and an API key (key:) each count separately, even when the agent or the key acts for that person. Every counter resets after a fixed one-minute window.
| Class | Routes | Requests a minute |
|---|---|---|
read | every GET under /v1/dev | 1200 |
create | POST /v1/dev/sessions (ciresk dev up, dev_session_up) | 120 |
ticket | POST /v1/dev/sessions/{session_id}/tickets, .../grants and .../web_handoff (connecting to a session, attaching a device, opening it in the browser from the console) | 600 |
write | every other POST, PUT, PATCH and DELETE that takes your credential | 300 |
The create limit is high because agents call ciresk dev up before each task, and that call returns the running session when one exists. The number of sessions you can actually run is set by your organization's policy (sessions_per_person, sessions_per_org, see admin.md), not by this limit.
These limits are set by whoever operates the installation: Ciresk for the cloud, your operator for a self-hosted one. Your organization cannot raise them. Each API server keeps its own counters, so in practice slightly more may get through. Do not build on that.
Limit per address for failed credentials
Every answer of 401 (an unknown, expired or revoked token or API key) counts against the network address the request came from. The limit is 120 a minute. After that, every request from that address answers 429 before its credential is even checked, until the window ends. This includes requests with a valid credential. If a script with a stale key shares an office's address with your team, fix the script; everyone behind that address waits out the minute.
Other limits you may meet
| What | Limit | Answer |
|---|---|---|
Needs raised by one session (ciresk dev needs raise, dev_session_needs_you) | 10 an hour | 429 rate_limited with retry_after |
| Browser links to sessions one person opens from the console | 30 a minute | 429 rate_limited with retry_after, the seconds until the oldest link of the minute leaves the window |
| Open tunnel streams per client to one session | 64 | client_stream_limit, retry after retry_after_ms (1000) |
The 429 answer
Over a limit, the API answers 429 Too Many Requests with a problem document (application/problem+json):
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 23
RateLimit-Limit: 120
RateLimit-Remaining: 0
RateLimit-Reset: 23
X-Request-Id: req_...{
"type": "https://docs.ciresk.com/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"detail": "More than 120 create requests a minute from this principal",
"code": "rate_limited",
"request_id": "req_...",
"retry_after": 23
}| Field or header | Meaning |
|---|---|
type | https://docs.ciresk.com/errors/rate_limited: rate_limited is a code every Ciresk API shares, so its link is the shared errors page; a code of Ciresk dev's own links to its entry in troubleshooting.md |
code | always rate_limited for these limits |
detail | which limit you hit: More than <n> <class> requests a minute from this principal, or Too many failed credentials from this address |
retry_after, Retry-After | seconds until the window ends, at least 1 |
RateLimit-Limit | the limit of the window you spent from |
RateLimit-Remaining | what is left in it, 0 on a 429 |
RateLimit-Reset | seconds until it resets |
Every answer to a request that took your credential carries the three RateLimit-* headers, not only a 429. You can read RateLimit-Remaining and slow down before you reach zero.
The first time an identity is refused in a window, your organization's audit log records dev.rate_limited with the class and the limit. Later refusals in the same window are not recorded again.
How ciresk dev and the MCP tools handle it
ciresk dev retries a 429 by itself, but only a call that is safe to send twice and only when the wait is short:
| Rule | |
|---|---|
| Which calls | every GET, PUT and DELETE, and a POST sent with an Idempotency-Key: ciresk dev down, ciresk dev stop, ciresk dev start and ciresk dev kill send one on every run, from --idempotency-key where the command takes it, otherwise a new key |
| How long it waits | the answer's retry_after (or retry_after_ms, or the Retry-After header when the body names neither), 1 second when it names nothing |
| How often | at most 2 more attempts |
| When it gives up at once | when the wait is over 5 seconds, or the call is a POST without a key, such as ciresk dev up or ciresk dev exec |
A per-minute limit usually names a longer wait than 5 seconds, so in practice the command reports it. It stops with the wait in the message and in next:
error: More than 120 create requests a minute from this principal; retry in 23 s (rate_limited, request req_...)
next: wait 23 s, then run the same command againIt exits with code 5, the code for limits and budgets. With --json, the envelope's error is the problem document above, including retry_after, and next is the same line. A 429 that names no wait says next: run the same command again in a minute.
The MCP tools (ciresk dev mcp) make their calls the same way, so the same retries happen inside a tool call. When one still fails, the tool result has isError: true, the problem in structuredContent (including retry_after) and the same next in _meta.ciresk/next. An agent should wait retry_after seconds before calling again.
Staying under the limits
- Wait the way the tools tell you to.
dev_session_wait,dev_session_exec_waitand the other long calls answer withpoll_after_mswhile work is running. Call again after that delay, never sooner, instead of polling in a loop of your own. - Use the session that is already running.
ciresk dev upreturns it at once. Do not stop and start a session for each task. - Retry a 429 after
retry_after. When you retry a stop, pause, start or kill yourself, send the sameIdempotency-Key(or--idempotency-key, or the MCP tool'sidempotency_key). An answer kept for that key within 24 hours is replayed, so the retry never acts twice. - Give each automated job its own API key or agent identity. Each one gets its own counters, and a loop in one does not use up another's.
- Fix or remove a script that keeps sending a revoked key. Its 401s count against your shared address.