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.

ClassRoutesRequests a minute
readevery GET under /v1/dev1200
createPOST /v1/dev/sessions (ciresk dev up, dev_session_up)120
ticketPOST /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
writeevery other POST, PUT, PATCH and DELETE that takes your credential300

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

WhatLimitAnswer
Needs raised by one session (ciresk dev needs raise, dev_session_needs_you)10 an hour429 rate_limited with retry_after
Browser links to sessions one person opens from the console30 a minute429 rate_limited with retry_after, the seconds until the oldest link of the minute leaves the window
Open tunnel streams per client to one session64client_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
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_...
JSON
{
  "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 headerMeaning
typehttps://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
codealways rate_limited for these limits
detailwhich limit you hit: More than <n> <class> requests a minute from this principal, or Too many failed credentials from this address
retry_after, Retry-Afterseconds until the window ends, at least 1
RateLimit-Limitthe limit of the window you spent from
RateLimit-Remainingwhat is left in it, 0 on a 429
RateLimit-Resetseconds 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 callsevery 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 waitsthe 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 oftenat most 2 more attempts
When it gives up at oncewhen 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:

Code
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 again

It 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_wait and the other long calls answer with poll_after_ms while 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 up returns 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 same Idempotency-Key (or --idempotency-key, or the MCP tool's idempotency_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.