Errors
The Cubicle HTTP API error envelope with code and message, every error code the public routes return (invalid_request, unauthenticated, forbidden, not_found, conflict, idempotency_conflict, rate_limited, capacity and more) with its HTTP status and what to do, conflict messages, and which errors are safe to retry.
Every error is a JSON object with an error member and a matching HTTP status:
{
"error": {
"code": "conflict",
"message": "a box named job-1 already exists"
}
}codeis machine-readable. Branch on it, not onmessage.messageis for people. Forinvalid_request, it usually names the field that failed and why.- Some errors add fields to
error.rate_limited, andblockedwhen the block has an end, addretryAfterSeconds, and send the same number of seconds in aRetry-Afterheader (example).
Treat a response without this envelope, such as a 502 or 504 with an HTML body, like a lost
connection: the request may or may not have reached the API.
Each endpoint page lists the errors that route can return, under its response statuses. The SDK
raises them as CubicleApiError, with the same status, code and message; see
Errors and retries.
Error codes
These are the codes the routes in the endpoint reference return.
| Code | Status | Meaning | What to do |
|---|---|---|---|
invalid_path | 400 | The URL, or the percent-encoding of a path segment, is malformed. | Fix the URL. Encode the slash in ORG/NAME as %2F. |
invalid_json | 400 | The body is not valid JSON. | Send a JSON body with Content-Type: application/json. |
invalid_request | 400 | The body, a parameter or a header fails validation. See Validation errors. | Fix what message names. Do not retry unchanged. |
invalid_key | 400 | Add an SSH key: the key is not a valid ssh-ed25519 public key. | Send one line from ~/.ssh/id_ed25519.pub. |
authorization_pending | 400 | Exchange a device code: nobody has authorized or denied the sign-in yet. | Poll again after interval seconds. |
access_denied | 400 | Device sign-in: the person chose Deny. | Stop polling. |
expired_token | 400 | Device sign-in: the code expired, 600 s after it was issued. | Stop polling, and start a new sign-in. |
invalid_grant | 400 | Device sign-in: the code is unknown, or was already exchanged for a session. | Stop polling. |
unauthenticated | 401 | The bearer token is missing, malformed, expired or revoked. See Authentication errors. | Fix the header, or get a new token. |
forbidden | 403 | You lack the role or the grant. message says which, such as an explicit box grant is required or box grant or organization admin role required. | Ask an owner or admin of the organization for a grant or a role. See Organizations and access. |
insufficient_scope | 403 | A read API key sent a request other than GET. | Use a write key. |
api_key_not_allowed | 403 | The route needs a sign-in session, and an API key was sent. | Use a sign-in session. |
box_create_disabled | 403 | The organization may not create Boxes. Personal organizations start this way. | Create the Box in an organization whose boxCreateAllowed is true in Get the caller, or ask a Cubicle operator to allow it. |
blocked | 403 | Your client address is blocked, after repeated rate-limit refusals or by an operator. | Send nothing until Retry-After seconds have passed. See Rate limits. |
not_found | 404 | The route or the resource does not exist, or you may not see it: it is in an organization where you are not an active member, outside an API key's organization, or a Box that is being deleted. | Check the path, the reference and the organization. After a delete, a 404 for the Box is expected. |
method_not_allowed | 405 | The path exists, but not with this method. | Check the method on the endpoint page. |
conflict | 409 | The request conflicts with the resource's current state. message says how; see Conflicts. | Depends on the message. |
ambiguous_box | 409 | A bare NAME matches Boxes in several of your organizations. | Use the Box's ID, ORG/NAME or ?org=. |
idempotency_conflict | 409 | The Idempotency-Key was already used for this route with a different body, or, for exec, snapshot and restore, with another Box. | Use a new key, or send the original body. See Idempotency keys. |
last_owner | 409 | Remove a member: the member is the organization's only active owner. | Make another member an owner first. |
payload_too_large | 413 | The body is larger than 64 KiB. | Send less. To get a large script or file into a Box, see Move files into and out of a Box. |
rate_limited | 429 | Your request budget, or a sign-in limit, is spent. retryAfterSeconds and Retry-After say when to retry. | Wait Retry-After seconds before any further request, then retry. See Rate limits. |
internal | 500 | The API failed unexpectedly. | Retry if the request is safe to repeat. |
capacity | 503 | No Node can place the Box when it is created: none has room, or the Node that holds its source snapshot is unavailable or full. A free Box (pooled: true) also gets it when its Node has no room to start it. Nothing was created or started. | Retry later, or ask for a smaller Box. |
logins_paused | 503 | Device sign-in: sign-in is paused, so no session can be issued now. | Try again later. |
Routes that only the Dashboard and the CLI use, such as the private network and the desktop console, have more codes. They are not listed here.
Validation errors
400 invalid_request means the request failed validation. message names the field and the
problem. Common causes:
- A required field is missing, or a field has the wrong type.
- A value is out of range, such as a Box size or an exec
timeoutMsunder 100 ms. - A
{user},{session}or{key}path segment is not a UUID. - An
Idempotency-Keyis not 1 to 200 characters. - A create names both
baseImageandsnapshotId. - A create from a snapshot asks for a smaller disk than the snapshot's.
Fix what message names. Do not retry the request unchanged.
Conflicts
409 conflict covers several situations. These are its messages on the public routes:
| Message | Returned by | What to do |
|---|---|---|
a box named job-1 already exists | Create a Box | Choose another name, or delete the other Box. |
service account ci already exists | Create a service account | Choose another name. |
resource already exists | Creating an API key whose name is taken, adding an SSH key you already have, or two requests racing to create the same thing | Use another name, or read the existing resource. |
box is not running | Run a command | Start the Box and wait for ssh.ready. |
stop the box before taking a snapshot | Take a snapshot | Stop the Box and wait for stopped at the current generation. |
stop the box before restoring a snapshot | Restore a Box | Stop the Box and wait for stopped at the current generation. |
snapshot is not complete | Create a Box from a snapshot, restore | Wait until the snapshot's operation is done. A failed snapshot cannot be used. |
only a running box can hibernate | Hibernate a Box | Hibernate only a Box whose desired state is running. |
the box requires an explicit Start after a host reboot | Hibernate | Start the Box. |
a snapshot or restore is in progress for this box | Start, stop, hibernate, delete | Wait until the snapshot or restore operation ends, then retry. |
box job-2 is still being created from a snapshot of this box | Delete a Box | Retry once the named Box has finished copying the snapshot. |
the box changed while starting; try again | Start a free Box (pooled: true) | Retry at once. |
Which errors are safe to retry
| Response | Retry? |
|---|---|
429 rate_limited | Yes, after Retry-After seconds. The request did nothing. |
403 blocked | Only after Retry-After seconds. Every request before then is refused. |
503 capacity, 503 logins_paused | Yes, later. Nothing was done. |
409 conflict for a snapshot or restore in progress, a Box still being created from a snapshot, or a free Box that changed while starting | Yes, once the cause has passed. |
400 authorization_pending | Yes: this is how you poll a device sign-in. |
500 internal, a timeout, a lost connection or a response without the envelope | Only when repeating the request is safe; see below. |
Any other 4xx | No. Change the request, the credentials or the Box's state first. |
After a 500, a timeout or a lost connection, the request may or may not have taken effect.
Repeating it is safe for:
- any
GET - create, exec, snapshot and restore sent again with the same
Idempotency-Key - start, stop and hibernate, which a repeat leaves in the same desired state. A repeated start or stop advances the Box's generation again.
- delete by ID, where a
404on the repeat means the first one went through
The SDK retries 429, 502, 503 and 504 for you; see Errors and retries.
Requests and waiting
How asynchronous Cubicle HTTP API requests work, with 202 Accepted and desired state, following a request by polling a Box's state, generation and ssh.ready, failure and superseded signals, deletion, exec, snapshot and restore operations, Idempotency-Key retries and polling budgets.
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.

