HTTP API
The Cubicle HTTP API at https://api.cubicle.sh/v1, with the base URL, a first authenticated curl request, JSON, UUID and ISO 8601 conventions, Box and organization references in paths, the OpenAPI 3.1 document for generating clients in other languages, and an index of every endpoint with its TypeScript SDK method.
The HTTP API is JSON over HTTPS. The SDK, the CLI and the Dashboard all use it. Every route is
under /v1:
https://api.cubicle.sh/v1The TypeScript SDK wraps this API: it waits for asynchronous requests, retries safely and paces its requests for you. There is no official SDK for other languages yet. From other languages, shell scripts and tools, call the HTTP API directly, or generate a client from the OpenAPI document.
Make a first request
You need an API key (cbk_…) that belongs to a service account. Sign in and API
keys shows how to create one.
export CB_SDK_API_KEY=cbk_…
curl https://api.cubicle.sh/v1/me \
-H "Authorization: Bearer $CB_SDK_API_KEY"The response says who the key belongs to:
{
"id": "8e0a2c4e-6b8d-4f1a-a3c5-9e1b3d5f7a9c",
"identity": "acme/ci",
"operator": false,
"orgs": [
{
"id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
"name": "acme",
"role": "member",
"personal": false,
"boxCreateAllowed": true
}
],
"apiKey": {
"id": "4b6d8f0a-2c4e-4a6b-8d0f-1a3c5e7b9d2f",
"orgId": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
"scope": "write"
}
}identityis the key's service account,ORG/NAME.orgsholds only the key's organization.boxCreateAllowedsays whether you can create Boxes in it.apiKey.scopeiswriteorread. Areadkey may only sendGETrequests.
Next, create a Box. The request returns 202 Accepted at once, with the Box in state pending:
curl -X POST https://api.cubicle.sh/v1/orgs/acme/boxes \
-H "Authorization: Bearer $CB_SDK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name": "job-1"}'Poll the Box until ssh.ready is true, as Following a
request describes, then run commands in it with
Run a command. Delete it with
Delete a Box when you are done.
Conventions
| Topic | Rule |
|---|---|
| Authentication | Authorization: Bearer <token> on every request except the two device sign-in routes. See Authentication. |
| Paths | Matched exactly, with no trailing slash. An unknown path returns 404 not_found. A known path with another method returns 405 method_not_allowed. |
| Request bodies | JSON, at most 64 KiB. Send Content-Type: application/json. An empty body counts as {}, so a route whose fields all have defaults needs no body. Unknown fields are ignored. |
| Responses | JSON, with content-type: application/json. A 204 response has no body. |
| Success statuses | 200 for reads and immediate changes, 201 when a service account, an API key or an SSH key is created, 202 when a request is accepted and carried out afterwards, and 204 when there is nothing to return. |
| IDs | UUIDs, such as 0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f. Boxes, organizations, users, service accounts, operations, snapshots, keys and sessions all have one. |
| Timestamps | ISO 8601 strings, such as 2026-11-05T10:14:21.000Z. Parse them with an ISO 8601 parser. |
| Sizes | Integers: vcpus, memoryMiB and diskGiB. |
| Durations | Milliseconds in requests (timeoutMs). Seconds in responses (ageSeconds, retryAfterSeconds, expiresIn, interval). |
| Lists | Bare JSON arrays, never wrapped in an object, and not paginated: a list returns every item. List a Box's operations is the exception: it returns the 50 most recent. |
| Response headers | Every response has cache-control: no-store, x-content-type-options: nosniff and x-frame-options: DENY. There are no CORS headers, so code in a web page cannot call the API, and no rate-limit headers. |
| Asynchronous work | Lifecycle changes, deletion, exec, snapshot and restore return 202. Poll until the work is done. See Requests and waiting. |
| Errors | { "error": { "code": "…", "message": "…" } } with a matching status. See Errors. |
| Rate limits | Each user or service account has a request budget. See Rate limits. |
Box and organization references
{box} in /v1/boxes/{box}/… takes a Box ID, ORG/NAME or a bare NAME. Box
references explains how each form resolves. In a path:
- Encode the slash in
ORG/NAMEas%2F, as in/v1/boxes/acme%2Fjob-1. Unencoded, the path names a different route. - Narrow a bare
NAMEwith?org=, which takes an organization's name or ID:/v1/boxes/job-1?org=acme.?org=is ignored when{box}is an ID.
{org} in /v1/orgs/{org}/… is the organization's name or ID. An organization where you are not an
active member returns 404 not_found, and so does every organization except an API key's own.
Version and OpenAPI document
/v1 is the only version of the API.
https://docs.cubicle.sh/openapi.json describes every public endpoint as an OpenAPI
3.1 document: parameters, request and response schemas, examples, errors and code samples
(x-codeSamples). The endpoint pages below are generated from it. Give it to a client generator, or
to a coding agent that needs the whole API. See Docs for AI tools for the plain-text docs.
Endpoints
Routes that accept API keys have an SDK method. The others, apart from the two device sign-in routes
that need no credentials, need a sign-in session; an API key gets 403 api_key_not_allowed there. See Authentication.
Boxes
Create Boxes, read them, and change their desired state.
| Method | Path | What it does | SDK method |
|---|---|---|---|
GET | /v1/orgs/{org}/boxes | Lists one organization's Boxes, including Boxes still being deleted. | cubicle.boxes.list({ org }) |
POST | /v1/orgs/{org}/boxes | Creates a Box from a base image or a snapshot. | cubicle.boxes.create() |
GET | /v1/boxes | Lists the Boxes of all your organizations, with grants and your access. | cubicle.boxes.list() without an org |
GET | /v1/boxes/{box} | Returns one Box with its desired and observed state. | cubicle.boxes.get(); cubicle.boxes.waitUntil() and waitUntilReady() poll it |
POST | /v1/boxes/{box}/start | Starts a Box, or resumes a hibernated one. | cubicle.boxes.start() |
POST | /v1/boxes/{box}/stop | Stops a Box and keeps its disk. | cubicle.boxes.stop() |
POST | /v1/boxes/{box}/hibernate | Saves a running Box's memory to its disk and stops it. | cubicle.boxes.hibernate() |
DELETE | /v1/boxes/{box} | Deletes a Box, its disk and its snapshots. | cubicle.boxes.delete() |
GET | /v1/orgs/{org}/bases | Lists the base images and their default sizes. | cubicle.listBases() |
Commands
Run commands in a Box and follow the operations that carry them out.
| Method | Path | What it does | SDK method |
|---|---|---|---|
POST | /v1/boxes/{box}/exec | Runs a command in a Box and returns its operation. | cubicle.boxes.exec() |
GET | /v1/boxes/{box}/operations | Lists a Box's 50 most recent operations. | cubicle.boxes.listOperations() |
GET | /v1/operations/{operation} | Returns one exec, snapshot or restore operation. | cubicle.operations.get(), cubicle.operations.wait() |
Snapshots
Save a stopped Box's disk, restore it, and create Boxes from snapshots.
| Method | Path | What it does | SDK method |
|---|---|---|---|
POST | /v1/boxes/{box}/snapshots | Takes a snapshot of a stopped Box's disk. | cubicle.boxes.snapshot() |
GET | /v1/boxes/{box}/snapshots | Lists a Box's snapshots, including pending and failed ones. | cubicle.boxes.listSnapshots() |
POST | /v1/boxes/{box}/restore | Replaces a stopped Box's disk with one of its snapshots. | cubicle.boxes.restore() |
GET | /v1/orgs/{org}/snapshots | Lists the complete snapshots you may create Boxes from. | cubicle.listSnapshots() |
Box access
Grant people and service accounts access to a Box, and read its SSH details.
| Method | Path | What it does | SDK method |
|---|---|---|---|
GET | /v1/boxes/{box}/grants | Lists a Box's grants, revoked ones included. | None |
PUT | /v1/boxes/{box}/grants/{user} | Gives a member of the organization a grant on a Box. | None |
DELETE | /v1/boxes/{box}/grants/{user} | Revokes a member's grant on a Box. | None |
GET | /v1/boxes/{box}/ssh | Returns your SSH user, the Box's host key and whether you can connect. | None |
Organizations
List and change an organization's members.
| Method | Path | What it does | SDK method |
|---|---|---|---|
GET | /v1/orgs/{org}/members | Lists an organization's members, people and service accounts. | None |
POST | /v1/orgs/{org}/members | Gives an existing user a role in the organization. | None |
DELETE | /v1/orgs/{org}/members/{user} | Ends a membership, or deletes a service account. | None |
Service accounts
Manage non-interactive members and their API keys.
| Method | Path | What it does | SDK method |
|---|---|---|---|
GET | /v1/orgs/{org}/service-accounts | Lists service accounts with their API keys. | None |
POST | /v1/orgs/{org}/service-accounts | Creates a service account. | None |
DELETE | /v1/orgs/{org}/service-accounts/{account} | Deletes a service account and revokes its keys. | None |
POST | /v1/orgs/{org}/service-accounts/{account}/keys | Creates an API key and returns its token once. | None |
DELETE | /v1/orgs/{org}/service-accounts/{account}/keys/{key} | Revokes an API key. | None |
Account
The signed-in caller, their sessions and their SSH keys.
| Method | Path | What it does | SDK method |
|---|---|---|---|
GET | /v1/me | Returns the caller and the organizations it can use. | cubicle.me() |
GET | /v1/sessions | Lists your live sign-in sessions. | None |
DELETE | /v1/sessions/{session} | Ends one of your sign-in sessions. | None |
GET | /v1/keys | Lists your SSH public keys. | None |
POST | /v1/keys | Adds an ssh-ed25519 public key to your account. | None |
DELETE | /v1/keys/{key} | Removes one of your SSH keys. | None |
Sign-in
Sign a device in with the device authorization flow, and sign out.
| Method | Path | What it does | SDK method |
|---|---|---|---|
POST | /v1/auth/device | Starts a device sign-in. Needs no credentials. | None |
POST | /v1/auth/device/token | Exchanges an approved device code for a sign-in session. | None |
POST | /v1/auth/logout | Ends the sign-in session that sends the request. | None |

