Cubicle

Rate limits

Cubicle HTTP API rate limits per user or service account (600 requests and 120 non-GET requests a minute), device sign-in limits, 429 rate_limited with Retry-After and retryAfterSeconds, strikes and the automatic 60-minute address block (403 blocked), and how to poll and retry within budget.

Cubicle limits how fast each account may send requests, and blocks client addresses that keep sending after they are refused. These are the limits on api.cubicle.sh. They may change.

Request budgets

Every authenticated request spends from the budget of the user or service account that sends it:

BudgetLimitCounts
All requests600 per minuteEvery authenticated request
Non-GET requests120 per minuteEvery request whose method is not GET
  • A POST, PUT or DELETE spends from both budgets.
  • A budget belongs to the account, not to a token. All API keys of one service account share it, as do all sessions of one person. A second key for the same service account adds no budget; a separate service account has its own.
  • A budget refills continuously, not once a minute. A full budget allows a burst of up to 600 requests (120 non-GET); after that, one more request fits every 100 ms, and one more non-GET request every 500 ms.
  • A refused request spends nothing.
  • A request that fails authentication is answered before any budget is checked, and spends nothing.

The device sign-in routes, which need no credentials, are limited per client address instead:

  • Start a device sign-in shares the sign-in limit with the Dashboard's other sign-in requests: 20 a minute per client address, and 300 a minute across all clients.
  • Exchange a device code may be called 120 times a minute per client address, and 3000 times a minute across all clients.

When you exceed a budget

A refused request gets 429 rate_limited. The Retry-After header and error.retryAfterSeconds both give the whole number of seconds, at least 1, until the request would fit:

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests; retry later",
    "retryAfterSeconds": 3
  }
}

Responses carry no X-RateLimit-* headers, so you cannot read how much budget is left. Pace your requests instead.

Repeated refusals block your address

  • Every 429 rate_limited is a strike against the client address that sent the request.
  • An address may collect 30 strikes an hour. The allowance refills by one strike every 2 minutes.
  • The next refusal blocks the address for 60 minutes.
  • A block covers everyone who sends from that address, whatever their account: for example, every CI job behind the same NAT gateway.

While the address is blocked, every authenticated request and every device sign-in request gets 403 blocked. Retry-After and retryAfterSeconds give the seconds until the block ends:

{
  "error": {
    "code": "blocked",
    "message": "this address is blocked",
    "retryAfterSeconds": 3517
  }
}

Cubicle operators can also block an address. Such a block may have no end: it then has no Retry-After, and lasts until it is lifted.

Stay within budget

  • Poll about once a second. One poll a second for each Box or operation you wait on is enough. See Requests and waiting.
  • Poll one listing for many Boxes. List an organization's Boxes returns every Box in one request, each with the same state, desired, observed and ssh fields as Get a Box.
  • Stop on 429. Wait Retry-After seconds before you send anything else from that client, not only before repeating the refused request: each further refusal is another strike.
  • Back off when there is no Retry-After. After a lost connection or a 502, 503 or 504, wait longer before each retry, for example 1 s, then 2 s, doubling up to 30 s.
  • Share the budget across workers. Parallel jobs that use the same service account, through one key or several, share 600 requests a minute. Spread their polling, or give independent workloads their own service accounts.

The SDK paces its polls and retries a 429 for you.

On this page