{
  "openapi": "3.1.0",
  "info": {
    "title": "Cubicle API",
    "version": "v1",
    "description": "The Cubicle HTTP API. Start with the [overview](https://docs.cubicle.sh/api/) for authentication, Box references, errors, rate limits and how to follow a request."
  },
  "servers": [
    {
      "url": "https://api.cubicle.sh",
      "description": "Cubicle API"
    }
  ],
  "tags": [
    {
      "name": "Boxes",
      "description": "Create Boxes, read them, and change their desired state."
    },
    {
      "name": "Commands",
      "description": "Run commands in a Box and follow the operations that carry them out."
    },
    {
      "name": "Snapshots",
      "description": "Save a stopped Box's disk, restore it, and create Boxes from snapshots."
    },
    {
      "name": "Box access",
      "description": "Grant people and service accounts access to a Box, and read its SSH details."
    },
    {
      "name": "Organizations",
      "description": "List and change an organization's members."
    },
    {
      "name": "Service accounts",
      "description": "Manage non-interactive members and their API keys."
    },
    {
      "name": "Account",
      "description": "The signed-in caller, their sessions and their SSH keys."
    },
    {
      "name": "Sign-in",
      "description": "Sign a device in with the device authorization flow, and sign out."
    }
  ],
  "paths": {
    "/v1/orgs/{org}/boxes": {
      "get": {
        "operationId": "list-org-boxes",
        "summary": "List an organization's Boxes",
        "description": "Returns every Box in `{org}`, oldest first, as a bare array. Any active member of the organization may list them, with or without grants; API keys may list only their own organization.\n\nA Box being deleted stays in this list with `deletion: \"pending\"` until its Node has removed it and released its resources. This list shows when a deletion has finished; [Get a Box](https://docs.cubicle.sh/api/reference/operations/get-box/) returns `404` as soon as the delete is accepted. See [Deleting a Box](https://docs.cubicle.sh/concepts/lifecycle/#deleting-a-box).",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's name or ID.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          }
        ],
        "responses": {
          "200": {
            "description": "The organization's Boxes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Box"
                  }
                },
                "example": [
                  {
                    "storage": {
                      "enabled": true,
                      "status": "base",
                      "checkpointId": null,
                      "checkpointAt": null,
                      "current": false,
                      "processedBytes": 0,
                      "totalBytes": 0,
                      "error": null,
                      "retryAt": null
                    },
                    "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                    "org": {
                      "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                      "name": "acme"
                    },
                    "name": "job-1",
                    "baseImage": "default",
                    "node": {
                      "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                      "name": "node-1",
                      "healthy": true
                    },
                    "spec": {
                      "vcpus": 2,
                      "memoryMiB": 2048,
                      "diskGiB": 12
                    },
                    "sourceSnapshotId": null,
                    "desired": {
                      "state": "running",
                      "generation": 1
                    },
                    "observed": {
                      "state": "running",
                      "generation": 1,
                      "observedAt": "2026-11-05T10:15:04.000Z",
                      "ageSeconds": 2,
                      "fresh": true,
                      "error": null
                    },
                    "state": "running",
                    "hibernateAt": "2026-11-05T11:14:58.000Z",
                    "hibernationRetryAt": null,
                    "pooled": false,
                    "expiresAt": null,
                    "restartRequired": false,
                    "connectionGeneration": 1,
                    "privateIp": "10.20.30.17",
                    "console": {
                      "ready": true,
                      "reason": null
                    },
                    "ssh": {
                      "ready": true,
                      "reason": null
                    },
                    "deletion": null,
                    "createdAt": "2026-11-05T10:14:21.000Z"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The caller is not an active member of `{org}`, it does not exist, or an API key belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The caller is not an active member of `{org}`, it does not exist, or an API key belongs to another organization."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/orgs/acme/boxes \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "for await (const info of cubicle.boxes.list({ org: \"acme\" })) {\n  console.log(info.name, info.state)\n}"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle boxes list --org acme"
          }
        ]
      },
      "post": {
        "operationId": "create-box",
        "summary": "Create a Box",
        "description": "Creates a Box in `{org}` from a base image or from a snapshot, and returns it with `202 Accepted`. The Box is not usable yet: poll [Get a Box](https://docs.cubicle.sh/api/reference/operations/get-box/) until `ssh.ready` is `true`, or, with `start: false`, until `state` is `stopped`. See [Following a request](https://docs.cubicle.sh/api/requests/#following-a-request).\n\nAny active member of the organization may create Boxes when the organization allows it (`boxCreateAllowed` in [Get the caller](https://docs.cubicle.sh/api/reference/operations/get-me/); see [Box creation approval](https://docs.cubicle.sh/concepts/access/#box-creation-approval)). API keys need the `write` scope.\n\n- **Name.** Unique among the organization's Boxes that are not deleted. Omit it to get `box-` followed by 6 random hex characters. A deleted Box's name is free again as soon as its delete is accepted.\n- **Source.** Set `baseImage` (default `default`) or `snapshotId`, not both; the schema below cannot express this rule. [List base images](https://docs.cubicle.sh/api/reference/operations/list-bases/) and [List snapshots to create Boxes from](https://docs.cubicle.sh/api/reference/operations/list-org-snapshots/) show the choices.\n- **Size.** Omitted `vcpus`, `memoryMiB` and `diskGiB` come from the base image, as [List base images](https://docs.cubicle.sh/api/reference/operations/list-bases/) shows, or from the source Box's current size for a snapshot.\n- **From a snapshot.** The caller needs a grant on the snapshot's source Box, or the owner or admin role. The snapshot must be complete and in `{org}`, and its source Box must not be deleted. The new Box keeps the source's base image, and its disk may be larger than the source's but not smaller.\n- **Placement.** Cubicle reserves the size for the Box's whole life on one healthy Node that has the base image, or, for a snapshot, on the Node that holds it; see [Resources](https://docs.cubicle.sh/concepts/boxes/#resources). When nothing fits, the request fails with `503 capacity` and nothing is created.\n- **Access.** The caller gets a [grant](https://docs.cubicle.sh/concepts/access/#box-grants) on the new Box.\n- **`start: false`** creates the Box stopped.\n\nWith an `Idempotency-Key`, repeating the request with the same body returns `200 OK` with the Box the first request created, as it is now (even if it has been deleted since), and creates nothing. [Idempotency keys](https://docs.cubicle.sh/api/requests/#idempotency-keys) has the rest of the rules, such as how long keys are kept. See [Boxes](https://docs.cubicle.sh/concepts/boxes/).",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's name or ID.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the request safe to retry: repeating it with the same key and body returns the first result instead of doing the work again. 1 to 200 characters; a UUID is a good choice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "4f1c7c1e-6a43-4d5e-9b7e-0d2f6c0e9a51"
          }
        ],
        "requestBody": {
          "required": false,
          "description": "Every field is optional, and an empty body creates a running `default` Box with a generated name. `snapshotId` and `baseImage` are mutually exclusive.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BoxCreateRequest"
              },
              "example": {
                "name": "job-1",
                "baseImage": "default",
                "vcpus": 2,
                "memoryMiB": 2048,
                "diskGiB": 12
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An `Idempotency-Key` replay: the Box the first request with this key created, as it is now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Box"
                },
                "example": {
                  "storage": {
                    "enabled": true,
                    "status": "base",
                    "checkpointId": null,
                    "checkpointAt": null,
                    "current": false,
                    "processedBytes": 0,
                    "totalBytes": 0,
                    "error": null,
                    "retryAt": null
                  },
                  "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "org": {
                    "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                    "name": "acme"
                  },
                  "name": "job-1",
                  "baseImage": "default",
                  "node": {
                    "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                    "name": "node-1",
                    "healthy": true
                  },
                  "spec": {
                    "vcpus": 2,
                    "memoryMiB": 2048,
                    "diskGiB": 12
                  },
                  "sourceSnapshotId": null,
                  "desired": {
                    "state": "running",
                    "generation": 1
                  },
                  "observed": {
                    "state": "running",
                    "generation": 1,
                    "observedAt": "2026-11-05T10:15:04.000Z",
                    "ageSeconds": 2,
                    "fresh": true,
                    "error": null
                  },
                  "state": "running",
                  "hibernateAt": "2026-11-05T11:14:58.000Z",
                  "hibernationRetryAt": null,
                  "pooled": false,
                  "expiresAt": null,
                  "restartRequired": false,
                  "connectionGeneration": 1,
                  "privateIp": "10.20.30.17",
                  "console": {
                    "ready": true,
                    "reason": null
                  },
                  "ssh": {
                    "ready": true,
                    "reason": null
                  },
                  "deletion": null,
                  "createdAt": "2026-11-05T10:14:21.000Z"
                }
              }
            }
          },
          "202": {
            "description": "The new Box, which its Node has not reported yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Box"
                },
                "example": {
                  "storage": {
                    "enabled": true,
                    "status": "base",
                    "checkpointId": null,
                    "checkpointAt": null,
                    "current": false,
                    "processedBytes": 0,
                    "totalBytes": 0,
                    "error": null,
                    "retryAt": null
                  },
                  "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "org": {
                    "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                    "name": "acme"
                  },
                  "name": "job-1",
                  "baseImage": "default",
                  "node": {
                    "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                    "name": "node-1",
                    "healthy": true
                  },
                  "spec": {
                    "vcpus": 2,
                    "memoryMiB": 2048,
                    "diskGiB": 12
                  },
                  "sourceSnapshotId": null,
                  "desired": {
                    "state": "running",
                    "generation": 1
                  },
                  "observed": null,
                  "state": "pending",
                  "hibernateAt": null,
                  "hibernationRetryAt": null,
                  "pooled": false,
                  "expiresAt": null,
                  "restartRequired": false,
                  "connectionGeneration": 1,
                  "privateIp": null,
                  "console": {
                    "ready": false,
                    "reason": "console_observation_stale"
                  },
                  "ssh": {
                    "ready": false,
                    "reason": "observation_stale"
                  },
                  "deletion": null,
                  "createdAt": "2026-11-05T10:14:21.000Z"
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_request`: Both `snapshotId` and `baseImage` are set, or `diskGiB` is smaller than the snapshot's disk.\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.\n- `invalid_request`: `Idempotency-Key` is not 1 to 200 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "Both `snapshotId` and `baseImage` are set, or `diskGiB` is smaller than the snapshot's disk."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `box_create_disabled`: The organization may not create Boxes; an operator must allow it.\n- `forbidden`: The caller holds no grant on the snapshot's source Box and is not an owner or admin of the organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `insufficient_scope`: A `read` API key was used; this route needs `write`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "box_create_disabled",
                    "message": "The organization may not create Boxes; an operator must allow it."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The caller is not an active member of `{org}`, it does not exist, or an API key belongs to another organization.\n- `not_found`: `snapshotId` is not a snapshot in `{org}`, or its source Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The caller is not an active member of `{org}`, it does not exist, or an API key belongs to another organization."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `conflict`: A Box with this name already exists in the organization and is not deleted, also when two creates race for the name.\n- `conflict`: The snapshot is not complete.\n- `idempotency_conflict`: The `Idempotency-Key` was already used for a create with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "A Box with this name already exists in the organization and is not deleted, also when two creates race for the name."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          },
          "503": {
            "description": "The service is unavailable.\n\n- `capacity`: No healthy Node with the base image has room for the Box, or, for a snapshot, the Node holding it is unavailable or full.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "capacity",
                    "message": "No healthy Node with the base image has room for the Box, or, for a snapshot, the Node holding it is unavailable or full."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/orgs/acme/boxes \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"job-1\",\"baseImage\":\"default\",\"vcpus\":2,\"memoryMiB\":2048,\"diskGiB\":12}'"
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "// Waits until the Box is ready; pass { wait: false } as a second argument to return at once.\nconst box = await cubicle.boxes.create({ org: \"acme\", name: \"job-1\" })"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle boxes create --org acme job-1"
          }
        ]
      }
    },
    "/v1/boxes": {
      "get": {
        "operationId": "list-boxes",
        "summary": "List the caller's Boxes",
        "description": "Returns every Box in every organization where the caller is an active member, as a bare array sorted by organization name and then creation time. An API key sees only its own organization's Boxes.\n\nEach entry is a [Box](https://docs.cubicle.sh/api/reference/operations/get-box/) with three more fields, described below: `viewer`, `grants` and `openVpnSessions`. Boxes being deleted are listed with `deletion: \"pending\"` until their Node has removed them.\n\nTo list one organization's Boxes, use [List an organization's Boxes](https://docs.cubicle.sh/api/reference/operations/list-org-boxes/).",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's Boxes, each with its grants and the caller's access.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/Box"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "viewer": {
                            "type": "object",
                            "description": "The caller's own access to the Box.",
                            "properties": {
                              "role": {
                                "anyOf": [
                                  {
                                    "$ref": "#/components/schemas/Role"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ],
                                "description": "The caller's role in the Box's organization."
                              },
                              "granted": {
                                "type": "boolean",
                                "description": "Whether the caller holds an active grant on the Box."
                              }
                            },
                            "required": [
                              "role",
                              "granted"
                            ]
                          },
                          "grants": {
                            "type": "array",
                            "description": "Every grant on the Box, revoked ones included, with each account's `accountStatus`.",
                            "items": {
                              "$ref": "#/components/schemas/Grant"
                            }
                          },
                          "openVpnSessions": {
                            "type": "integer",
                            "description": "How many open private-network sessions currently route to the Box."
                          }
                        },
                        "required": [
                          "viewer",
                          "grants",
                          "openVpnSessions"
                        ]
                      }
                    ]
                  }
                },
                "example": [
                  {
                    "storage": {
                      "enabled": true,
                      "status": "base",
                      "checkpointId": null,
                      "checkpointAt": null,
                      "current": false,
                      "processedBytes": 0,
                      "totalBytes": 0,
                      "error": null,
                      "retryAt": null
                    },
                    "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                    "org": {
                      "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                      "name": "acme"
                    },
                    "name": "job-1",
                    "baseImage": "default",
                    "node": {
                      "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                      "name": "node-1",
                      "healthy": true
                    },
                    "spec": {
                      "vcpus": 2,
                      "memoryMiB": 2048,
                      "diskGiB": 12
                    },
                    "sourceSnapshotId": null,
                    "desired": {
                      "state": "running",
                      "generation": 1
                    },
                    "observed": {
                      "state": "running",
                      "generation": 1,
                      "observedAt": "2026-11-05T10:15:04.000Z",
                      "ageSeconds": 2,
                      "fresh": true,
                      "error": null
                    },
                    "state": "running",
                    "hibernateAt": "2026-11-05T11:14:58.000Z",
                    "hibernationRetryAt": null,
                    "pooled": false,
                    "expiresAt": null,
                    "restartRequired": false,
                    "connectionGeneration": 1,
                    "privateIp": "10.20.30.17",
                    "console": {
                      "ready": true,
                      "reason": null
                    },
                    "ssh": {
                      "ready": true,
                      "reason": null
                    },
                    "deletion": null,
                    "createdAt": "2026-11-05T10:14:21.000Z",
                    "viewer": {
                      "role": "member",
                      "granted": true
                    },
                    "grants": [
                      {
                        "userId": "2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a",
                        "identity": "ada@example.com",
                        "status": "active",
                        "guestAccount": "jv_2c4e6a8b0d1f",
                        "desiredRevision": 2,
                        "appliedRevision": 2,
                        "accountStatus": "applied"
                      }
                    ],
                    "openVpnSessions": 1
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/boxes \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "// Without an org option (on the client or here), the SDK lists every organization's Boxes.\nfor await (const info of cubicle.boxes.list()) {\n  console.log(`${info.org.name}/${info.name}`, info.state)\n}"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle boxes list"
          }
        ]
      }
    },
    "/v1/boxes/{box}": {
      "get": {
        "operationId": "get-box",
        "summary": "Get a Box",
        "description": "Returns the Box as Cubicle sees it now. Reading a Box never changes it. Any active member of the Box's organization may read it.\n\nPoll this route to follow a lifecycle request: see [Following a request](https://docs.cubicle.sh/api/requests/#following-a-request).",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Box.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Box"
                },
                "example": {
                  "storage": {
                    "enabled": true,
                    "status": "base",
                    "checkpointId": null,
                    "checkpointAt": null,
                    "current": false,
                    "processedBytes": 0,
                    "totalBytes": 0,
                    "error": null,
                    "retryAt": null
                  },
                  "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "org": {
                    "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                    "name": "acme"
                  },
                  "name": "job-1",
                  "baseImage": "default",
                  "node": {
                    "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                    "name": "node-1",
                    "healthy": true
                  },
                  "spec": {
                    "vcpus": 2,
                    "memoryMiB": 2048,
                    "diskGiB": 12
                  },
                  "sourceSnapshotId": null,
                  "desired": {
                    "state": "running",
                    "generation": 1
                  },
                  "observed": {
                    "state": "running",
                    "generation": 1,
                    "observedAt": "2026-11-05T10:15:04.000Z",
                    "ageSeconds": 2,
                    "fresh": true,
                    "error": null
                  },
                  "state": "running",
                  "hibernateAt": "2026-11-05T11:14:58.000Z",
                  "hibernationRetryAt": null,
                  "pooled": false,
                  "expiresAt": null,
                  "restartRequired": false,
                  "connectionGeneration": 1,
                  "privateIp": "10.20.30.17",
                  "console": {
                    "ready": true,
                    "reason": null
                  },
                  "ssh": {
                    "ready": true,
                    "reason": null
                  },
                  "deletion": null,
                  "createdAt": "2026-11-05T10:14:21.000Z"
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/boxes/acme%2Fjob-1 \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "const info = await cubicle.boxes.get(\"acme/job-1\")"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle boxes get acme/job-1"
          }
        ]
      },
      "delete": {
        "operationId": "delete-box",
        "summary": "Delete a Box",
        "description": "Deletes the Box and returns it with `202 Accepted`, `desired.state: \"deleted\"` and `deletion: \"pending\"`. Deletion cannot be undone.\n\nThe caller needs a grant on the Box, or the owner or admin role in its organization. API keys need the `write` scope.\n\nAs soon as the delete is accepted:\n\n- this route, [Get a Box](https://docs.cubicle.sh/api/reference/operations/get-box/) and every other `/v1/boxes/{box}` route return `404`, and the Box's name can be used for a new Box;\n- operations that are still pending fail with `box deleted`, and running commands end with state `unknown`.\n\nThe Box's Node then turns the guest off without shutting it down and removes its disk and all of its snapshots. Until it has, [List an organization's Boxes](https://docs.cubicle.sh/api/reference/operations/list-org-boxes/) and [List the caller's Boxes](https://docs.cubicle.sh/api/reference/operations/list-boxes/) show the Box with `deletion: \"pending\"`; once it leaves them, its CPU, memory, disk and private IP are released. Boxes created from its snapshots are not affected.\n\nSee [Deleting a Box](https://docs.cubicle.sh/concepts/lifecycle/#deleting-a-box).",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "The Box, now being deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Box"
                },
                "example": {
                  "storage": {
                    "enabled": true,
                    "status": "unprotected",
                    "checkpointId": null,
                    "checkpointAt": null,
                    "current": false,
                    "processedBytes": 0,
                    "totalBytes": 0,
                    "error": null,
                    "retryAt": null
                  },
                  "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "org": {
                    "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                    "name": "acme"
                  },
                  "name": "job-1",
                  "baseImage": "default",
                  "node": {
                    "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                    "name": "node-1",
                    "healthy": true
                  },
                  "spec": {
                    "vcpus": 2,
                    "memoryMiB": 2048,
                    "diskGiB": 12
                  },
                  "sourceSnapshotId": null,
                  "desired": {
                    "state": "deleted",
                    "generation": 2
                  },
                  "observed": {
                    "state": "running",
                    "generation": 1,
                    "observedAt": "2026-11-05T10:15:04.000Z",
                    "ageSeconds": 2,
                    "fresh": true,
                    "error": null
                  },
                  "state": "running",
                  "hibernateAt": null,
                  "hibernationRetryAt": null,
                  "pooled": false,
                  "expiresAt": null,
                  "restartRequired": false,
                  "connectionGeneration": 2,
                  "privateIp": "10.20.30.17",
                  "console": {
                    "ready": false,
                    "reason": "deleted"
                  },
                  "ssh": {
                    "ready": false,
                    "reason": "deleted"
                  },
                  "deletion": "pending",
                  "createdAt": "2026-11-05T10:14:21.000Z"
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller holds no grant on the Box and is not an owner or admin of its organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `insufficient_scope`: A `read` API key was used; this route needs `write`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller holds no grant on the Box and is not an owner or admin of its organization."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.\n- `conflict`: A snapshot or restore of the Box is pending or running.\n- `conflict`: A Box created from one of this Box's snapshots is still copying it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X DELETE https://api.cubicle.sh/v1/boxes/acme%2Fjob-1 \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "// Waits until the Node has removed the Box; pass { wait: false } to return at once.\nawait cubicle.boxes.delete(\"acme/job-1\")"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle boxes delete acme/job-1"
          }
        ]
      }
    },
    "/v1/boxes/{box}/start": {
      "post": {
        "operationId": "start-box",
        "summary": "Start a Box",
        "description": "Sets the Box's desired state to `running` and returns it with `202 Accepted`. A stopped Box boots; a hibernated Box resumes from its saved memory; a `failed` Box tries again. Poll [Get a Box](https://docs.cubicle.sh/api/reference/operations/get-box/) until `ssh.ready` is `true`. Until then, `state` and `observed` show the Node's last report; see [Following a request](https://docs.cubicle.sh/api/requests/#following-a-request).\n\nThe caller needs a grant on the Box, or the owner or admin role in its organization. API keys need the `write` scope.\n\nEvery accepted start advances `desired.generation` and `connectionGeneration`, even when the Box is already running. On a running Box it does not reboot the guest, but until the Node has applied the new generation, `ssh.ready` is `false` and private-network sessions cannot reach the Box. It also restarts the 60-minute hibernation clock. A start also clears `restartRequired` after a host restart and cancels a pending hibernation retry.\n\nSee [Box lifecycle](https://docs.cubicle.sh/concepts/lifecycle/).",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "The Box with its new desired state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Box"
                },
                "example": {
                  "storage": {
                    "enabled": true,
                    "status": "base",
                    "checkpointId": null,
                    "checkpointAt": null,
                    "current": false,
                    "processedBytes": 0,
                    "totalBytes": 0,
                    "error": null,
                    "retryAt": null
                  },
                  "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "org": {
                    "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                    "name": "acme"
                  },
                  "name": "job-1",
                  "baseImage": "default",
                  "node": {
                    "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                    "name": "node-1",
                    "healthy": true
                  },
                  "spec": {
                    "vcpus": 2,
                    "memoryMiB": 2048,
                    "diskGiB": 12
                  },
                  "sourceSnapshotId": null,
                  "desired": {
                    "state": "running",
                    "generation": 3
                  },
                  "observed": {
                    "state": "stopped",
                    "generation": 2,
                    "observedAt": "2026-11-05T10:15:04.000Z",
                    "ageSeconds": 2,
                    "fresh": true,
                    "error": null
                  },
                  "state": "stopped",
                  "hibernateAt": null,
                  "hibernationRetryAt": null,
                  "pooled": false,
                  "expiresAt": null,
                  "restartRequired": false,
                  "connectionGeneration": 3,
                  "privateIp": "10.20.30.17",
                  "console": {
                    "ready": false,
                    "reason": "console_observation_stale"
                  },
                  "ssh": {
                    "ready": false,
                    "reason": "not_running"
                  },
                  "deletion": null,
                  "createdAt": "2026-11-05T10:14:21.000Z"
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller holds no grant on the Box and is not an owner or admin of its organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `insufficient_scope`: A `read` API key was used; this route needs `write`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller holds no grant on the Box and is not an owner or admin of its organization."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.\n- `conflict`: A snapshot or restore of the Box is pending or running.\n- `conflict`: A free Box changed while it was being started; retry the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          },
          "503": {
            "description": "The service is unavailable.\n\n- `capacity`: A free Box's CPU and memory were released and its Node has no room for them now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "capacity",
                    "message": "A free Box's CPU and memory were released and its Node has no room for them now."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/start \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "// Waits until the Box is ready; pass { wait: false } to return at once.\nawait cubicle.boxes.start(\"acme/job-1\")"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle boxes start acme/job-1"
          }
        ]
      }
    },
    "/v1/boxes/{box}/stop": {
      "post": {
        "operationId": "stop-box",
        "summary": "Stop a Box",
        "description": "Sets the Box's desired state to `stopped` and returns it with `202 Accepted`. The Node asks the guest to shut down, and turns it off if it has not stopped about 15 seconds later. Poll [Get a Box](https://docs.cubicle.sh/api/reference/operations/get-box/) until `state` is `stopped` and `observed.generation` equals `desired.generation`. Until then, `state` and `observed` show the Node's last report; see [Following a request](https://docs.cubicle.sh/api/requests/#following-a-request).\n\nThe caller needs a grant on the Box, or the owner or admin role in its organization. API keys need the `write` scope.\n\nThe Box keeps its disk, its private IP and its reserved CPU, memory and disk space. Its memory and running processes are lost, as is a hibernated Box's saved memory, and commands still running end with state `unknown`. Every accepted stop advances `desired.generation` and `connectionGeneration`, even when the Box is already stopped, and clears `restartRequired`. Snapshots and restores need a stopped Box.\n\nSee [What stop, hibernate and delete keep](https://docs.cubicle.sh/concepts/lifecycle/#what-stop-hibernate-and-delete-keep).",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "The Box with its new desired state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Box"
                },
                "example": {
                  "storage": {
                    "enabled": true,
                    "status": "unprotected",
                    "checkpointId": null,
                    "checkpointAt": null,
                    "current": false,
                    "processedBytes": 0,
                    "totalBytes": 0,
                    "error": null,
                    "retryAt": null
                  },
                  "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "org": {
                    "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                    "name": "acme"
                  },
                  "name": "job-1",
                  "baseImage": "default",
                  "node": {
                    "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                    "name": "node-1",
                    "healthy": true
                  },
                  "spec": {
                    "vcpus": 2,
                    "memoryMiB": 2048,
                    "diskGiB": 12
                  },
                  "sourceSnapshotId": null,
                  "desired": {
                    "state": "stopped",
                    "generation": 2
                  },
                  "observed": {
                    "state": "running",
                    "generation": 1,
                    "observedAt": "2026-11-05T10:15:04.000Z",
                    "ageSeconds": 2,
                    "fresh": true,
                    "error": null
                  },
                  "state": "running",
                  "hibernateAt": null,
                  "hibernationRetryAt": null,
                  "pooled": false,
                  "expiresAt": null,
                  "restartRequired": false,
                  "connectionGeneration": 2,
                  "privateIp": "10.20.30.17",
                  "console": {
                    "ready": false,
                    "reason": "not_requested"
                  },
                  "ssh": {
                    "ready": false,
                    "reason": "not_requested"
                  },
                  "deletion": null,
                  "createdAt": "2026-11-05T10:14:21.000Z"
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller holds no grant on the Box and is not an owner or admin of its organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `insufficient_scope`: A `read` API key was used; this route needs `write`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller holds no grant on the Box and is not an owner or admin of its organization."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.\n- `conflict`: A snapshot or restore of the Box is pending or running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/stop \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "// Waits until the Box is stopped; pass { wait: false } to return at once.\nawait cubicle.boxes.stop(\"acme/job-1\")"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle boxes stop acme/job-1"
          }
        ]
      }
    },
    "/v1/boxes/{box}/hibernate": {
      "post": {
        "operationId": "hibernate-box",
        "summary": "Hibernate a Box",
        "description": "Sets a running Box's desired state to `hibernated` and returns it with `202 Accepted`. The Node saves the guest's memory and device state to the Box's disk and stops it; [Start a Box](https://docs.cubicle.sh/api/reference/operations/start-box/) resumes it with its processes intact. Poll [Get a Box](https://docs.cubicle.sh/api/reference/operations/get-box/) until `state` is `hibernated` and `observed.generation` equals `desired.generation`. Until then, `state` and `observed` show the Node's last report; see [Following a request](https://docs.cubicle.sh/api/requests/#following-a-request).\n\nThe caller needs a grant on the Box, or the owner or admin role in its organization. API keys need the `write` scope.\n\n- The Box must be desired `running`. Hibernating a Box that is already desired `hibernated` changes nothing and returns `202`.\n- Commands still running end with state `unknown`. The Box keeps its private IP and its reserved CPU, memory and disk.\n- If saving fails, the guest keeps running: `desired.state` returns to `running` at a new generation, `observed.error` says why, and Cubicle retries at `hibernationRetryAt`.\n\nRunning Boxes also hibernate on their own at `hibernateAt`; see [Box lifecycle](https://docs.cubicle.sh/concepts/lifecycle/#boxes-hibernate-60-minutes-after-each-start). There is no CLI command for this route. See [Stop, hibernate and wake a Box](https://docs.cubicle.sh/guides/hibernation/).",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "The Box with its new desired state, or unchanged if it was already desired `hibernated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Box"
                },
                "example": {
                  "storage": {
                    "enabled": true,
                    "status": "unprotected",
                    "checkpointId": null,
                    "checkpointAt": null,
                    "current": false,
                    "processedBytes": 0,
                    "totalBytes": 0,
                    "error": null,
                    "retryAt": null
                  },
                  "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "org": {
                    "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                    "name": "acme"
                  },
                  "name": "job-1",
                  "baseImage": "default",
                  "node": {
                    "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                    "name": "node-1",
                    "healthy": true
                  },
                  "spec": {
                    "vcpus": 2,
                    "memoryMiB": 2048,
                    "diskGiB": 12
                  },
                  "sourceSnapshotId": null,
                  "desired": {
                    "state": "hibernated",
                    "generation": 2
                  },
                  "observed": {
                    "state": "running",
                    "generation": 1,
                    "observedAt": "2026-11-05T10:15:04.000Z",
                    "ageSeconds": 2,
                    "fresh": true,
                    "error": null
                  },
                  "state": "running",
                  "hibernateAt": null,
                  "hibernationRetryAt": null,
                  "pooled": false,
                  "expiresAt": null,
                  "restartRequired": false,
                  "connectionGeneration": 2,
                  "privateIp": "10.20.30.17",
                  "console": {
                    "ready": false,
                    "reason": "not_requested"
                  },
                  "ssh": {
                    "ready": false,
                    "reason": "not_requested"
                  },
                  "deletion": null,
                  "createdAt": "2026-11-05T10:14:21.000Z"
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller holds no grant on the Box and is not an owner or admin of its organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `insufficient_scope`: A `read` API key was used; this route needs `write`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller holds no grant on the Box and is not an owner or admin of its organization."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.\n- `conflict`: A snapshot or restore of the Box is pending or running.\n- `conflict`: The Box is not desired `running` (it is stopped or stopping).\n- `conflict`: `restartRequired` is `true`: the Box must be started after its host restarted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/hibernate \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "// Waits until the Box is hibernated; pass { wait: false } to return at once.\nawait cubicle.boxes.hibernate(\"acme/job-1\")"
          }
        ]
      }
    },
    "/v1/boxes/{box}/scale": {
      "post": {
        "operationId": "scale-box",
        "summary": "Increase a Box's memory",
        "description": "Sets the Box's memory to `memoryMiB` and returns it with `202 Accepted`. Memory can only grow, and the Box must be fully stopped: desired and observed `stopped`, with `observed.generation` equal to `desired.generation`. [Stop a Box](https://docs.cubicle.sh/api/reference/operations/stop-box/) first and wait for it. The next [start](https://docs.cubicle.sh/api/reference/operations/start-box/) boots the Box with its new memory.\n\nThe caller needs a grant on the Box, or the owner or admin role in its organization. API keys need the `write` scope.\n\nCubicle reserves the extra memory on the Box's Node when it accepts the request, and advances `desired.generation`. Asking for the Box's current memory changes nothing and returns `202`. CPU and disk cannot be changed.",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The Box's new memory in MiB, at least its current memory.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BoxScaleRequest"
              },
              "example": {
                "memoryMiB": 4096
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The Box with its new memory and desired generation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Box"
                },
                "example": {
                  "storage": {
                    "enabled": true,
                    "status": "unprotected",
                    "checkpointId": null,
                    "checkpointAt": null,
                    "current": false,
                    "processedBytes": 0,
                    "totalBytes": 0,
                    "error": null,
                    "retryAt": null
                  },
                  "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "org": {
                    "id": "6b1f3d2a-8c4e-4f7a-9b2d-1e5c7a9f3b6d",
                    "name": "acme"
                  },
                  "name": "job-1",
                  "baseImage": "default",
                  "node": {
                    "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                    "name": "node-1",
                    "healthy": true
                  },
                  "spec": {
                    "vcpus": 2,
                    "memoryMiB": 4096,
                    "diskGiB": 12
                  },
                  "sourceSnapshotId": null,
                  "desired": {
                    "state": "stopped",
                    "generation": 3
                  },
                  "observed": {
                    "state": "running",
                    "generation": 1,
                    "observedAt": "2026-11-05T10:15:04.000Z",
                    "ageSeconds": 2,
                    "fresh": true,
                    "error": null
                  },
                  "state": "stopped",
                  "hibernateAt": null,
                  "hibernationRetryAt": null,
                  "pooled": false,
                  "expiresAt": null,
                  "restartRequired": false,
                  "connectionGeneration": 2,
                  "privateIp": "10.20.30.17",
                  "console": {
                    "ready": false,
                    "reason": "not_requested"
                  },
                  "ssh": {
                    "ready": false,
                    "reason": "not_requested"
                  },
                  "deletion": null,
                  "createdAt": "2026-11-05T10:14:21.000Z"
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "The body is not valid JSON."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller holds no grant on the Box and is not an owner or admin of its organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `insufficient_scope`: A `read` API key was used; this route needs `write`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller holds no grant on the Box and is not an owner or admin of its organization."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.\n- `conflict`: A snapshot or restore of the Box is pending or running.\n- `conflict`: `memoryMiB` is less than the Box's current memory.\n- `conflict`: The Box is not fully stopped: stop it and wait until `state` is `stopped` at the desired generation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          },
          "503": {
            "description": "The service is unavailable.\n\n- `capacity`: The Box's Node has no free memory for the increase.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "capacity",
                    "message": "The Box's Node has no free memory for the increase."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/scale \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"memoryMiB\":4096}'"
          }
        ]
      }
    },
    "/v1/orgs/{org}/bases": {
      "get": {
        "operationId": "list-bases",
        "summary": "List base images",
        "description": "Returns the base images a Box can be created from, as a bare array: `default` and `omarchy`, each with the size a new Box gets when the create request omits it. Any active member of `{org}` may list them; the list is the same for every organization.\n\n`available` is `true` when at least one healthy Node offers the image. It does not check capacity, so [Create a Box](https://docs.cubicle.sh/api/reference/operations/create-box/) can still fail with `503 capacity`.\n\nSee [Boxes](https://docs.cubicle.sh/concepts/boxes/).",
        "tags": [
          "Boxes"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's name or ID.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          }
        ],
        "responses": {
          "200": {
            "description": "The base images.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CreationBase"
                  }
                },
                "example": [
                  {
                    "id": "default",
                    "name": "Default",
                    "spec": {
                      "vcpus": 2,
                      "memoryMiB": 2048,
                      "diskGiB": 12
                    },
                    "available": true
                  },
                  {
                    "id": "omarchy",
                    "name": "Omarchy",
                    "spec": {
                      "vcpus": 4,
                      "memoryMiB": 8192,
                      "diskGiB": 40
                    },
                    "available": true
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The caller is not an active member of `{org}`, it does not exist, or an API key belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The caller is not an active member of `{org}`, it does not exist, or an API key belongs to another organization."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/orgs/acme/bases \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "const bases = await cubicle.listBases({ org: \"acme\" })"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle boxes bases --org acme"
          }
        ]
      }
    },
    "/v1/boxes/{box}/exec": {
      "post": {
        "operationId": "run-command",
        "summary": "Run a command",
        "description": "Queues `argv` to run in the Box and returns an [operation](https://docs.cubicle.sh/api/reference/operations/get-operation/) with `202 Accepted`. Poll the operation until its `state` is `done`, `failed` or `unknown`; a done exec's `result` holds the exit code and output. See [Following a request](https://docs.cubicle.sh/api/requests/#following-a-request).\n\nThe caller needs an explicit grant on the Box: the owner or admin role is not enough. API keys need the `write` scope, and their service account needs the grant.\n\n- The Box must be desired `running`. Wait until `ssh.ready` is `true` first: an exec accepted before the Box is running can fail with `box is not running at the requested generation`.\n- The command runs as the caller's [guest account](https://docs.cubicle.sh/concepts/access/#guest-accounts), in its home directory unless `cwd` is set, without a shell, a TTY or stdin; use `[\"sh\", \"-c\", \"…\"]` for shell syntax. It has passwordless `sudo` only once the caller has an SSH key, so a service account's commands cannot rely on `sudo`; see [Root and sudo](https://docs.cubicle.sh/concepts/commands/#root-and-sudo).\n- `timeoutMs` defaults to 60,000 (60 s), and larger values than 600,000 (10 min) are lowered to it without an error. A command that runs out of time is killed and reports `timedOut: true` and exit code `124`.\n- `maxOutputBytes` defaults to 65,536 (64 KiB), and larger values than 1,048,576 (1 MiB) are lowered to it. It is one budget for stdout and stderr together; the rest is discarded and `truncated` is `true`.\n- A non-zero exit code or a timeout still ends `done`. `failed` comes with an `error`, for example when the Box stopped or the caller lost its grant before the command started. `unknown` means it was interrupted and may or may not have run. Neither is run again; see [Operations record every request](https://docs.cubicle.sh/concepts/commands/#operations-record-every-request).\n- Only the caller sees the result. Other members of the organization can read the operation, but its `result` is `null` for them.\n\nWith an `Idempotency-Key`, repeating the request with the same body returns `202` with the operation the first request created, and runs nothing. Output is returned only when the command ends. See [Run commands](https://docs.cubicle.sh/guides/run-commands/).",
        "tags": [
          "Commands"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the request safe to retry: repeating it with the same key and body returns the first result instead of doing the work again. 1 to 200 characters; a UUID is a good choice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "4f1c7c1e-6a43-4d5e-9b7e-0d2f6c0e9a51"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExecRequest"
              },
              "example": {
                "argv": [
                  "uname",
                  "-a"
                ],
                "timeoutMs": 30000
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The exec operation: new, or the original one for an `Idempotency-Key` replay.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Operation"
                },
                "example": {
                  "id": "9d2e4f6a-1b3c-4d5e-8f7a-6b8c0d2e4f1a",
                  "boxId": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "type": "exec",
                  "state": "pending",
                  "attempt": 0,
                  "progress": null,
                  "result": null,
                  "error": null,
                  "createdAt": "2026-11-05T10:16:00.000Z",
                  "startedAt": null,
                  "completedAt": null
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.\n- `invalid_request`: `Idempotency-Key` is not 1 to 200 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "The body is not valid JSON."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller holds no grant on the Box. The owner or admin role is not enough.\n- `blocked`: The client address is blocked after repeated refusals.\n- `insufficient_scope`: A `read` API key was used; this route needs `write`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller holds no grant on the Box. The owner or admin role is not enough."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.\n- `conflict`: The Box is not desired `running`.\n- `idempotency_conflict`: The `Idempotency-Key` was already used for an exec with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/exec \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"argv\":[\"uname\",\"-a\"],\"timeoutMs\":30000}'"
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "// Waits for the command to finish.\nconst result = await cubicle.boxes.exec(\"acme/job-1\", [\"uname\", \"-a\"], { timeoutMs: 30_000 })\n// => { exitCode: 0, stdout: \"Linux box-job-1 …\", stderr: \"\", timedOut: false, truncated: false, operationId: \"…\" }"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle exec acme/job-1 --timeout 30000 -- uname -a"
          }
        ]
      }
    },
    "/v1/boxes/{box}/operations": {
      "get": {
        "operationId": "list-box-operations",
        "summary": "List a Box's operations",
        "description": "Returns the Box's 50 most recent operations of every type (`exec`, `snapshot` and `restore`), newest first, as a bare array. There are no filters or pages.\n\nAny active member of the Box's organization may list them, without a grant. An exec's `result` is `null` unless the caller requested it.\n\nSee [Commands and operations](https://docs.cubicle.sh/concepts/commands/).",
        "tags": [
          "Commands"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Up to 50 operations, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Operation"
                  }
                },
                "example": [
                  {
                    "id": "6c8e0a2c-4e6a-4b8c-9e0a-4c6e8a0c2e5b",
                    "boxId": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                    "type": "restore",
                    "state": "done",
                    "attempt": 1,
                    "progress": null,
                    "result": {
                      "snapshotId": "5a7c9e1b-3d5f-4a6b-8c0d-2e4f6a8b0c3d"
                    },
                    "error": null,
                    "createdAt": "2026-11-05T10:30:00.000Z",
                    "startedAt": "2026-11-05T10:30:00.300Z",
                    "completedAt": "2026-11-05T10:30:41.000Z"
                  },
                  {
                    "id": "9d2e4f6a-1b3c-4d5e-8f7a-6b8c0d2e4f1a",
                    "boxId": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                    "type": "exec",
                    "state": "done",
                    "attempt": 1,
                    "progress": null,
                    "result": {
                      "exitCode": 0,
                      "stdout": "Linux box-job-1 6.12.8 #1 SMP x86_64 GNU/Linux\n",
                      "stderr": "",
                      "timedOut": false,
                      "truncated": false
                    },
                    "error": null,
                    "createdAt": "2026-11-05T10:16:00.000Z",
                    "startedAt": "2026-11-05T10:16:00.400Z",
                    "completedAt": "2026-11-05T10:16:00.900Z"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/operations \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "const operations = await cubicle.boxes.listOperations(\"acme/job-1\")"
          }
        ]
      }
    },
    "/v1/operations/{operation}": {
      "get": {
        "operationId": "get-operation",
        "summary": "Get an operation",
        "description": "Returns one exec, snapshot or restore operation. Poll it after [Run a command](https://docs.cubicle.sh/api/reference/operations/run-command/), [Take a snapshot](https://docs.cubicle.sh/api/reference/operations/create-snapshot/) or [Restore a Box](https://docs.cubicle.sh/api/reference/operations/restore-box/) until its `state` is `done`, `failed` or `unknown`. See [Following a request](https://docs.cubicle.sh/api/requests/#following-a-request).\n\nAny active member of the operation's organization may read it, without a grant on the Box, also after the Box is deleted. API keys may read only their own organization's operations. An exec's `result` is `null` unless the caller requested it.\n\nSee [Commands and operations](https://docs.cubicle.sh/concepts/commands/).",
        "tags": [
          "Commands"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "operation",
            "in": "path",
            "description": "The operation's ID, from the response that created it.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "9d2e4f6a-1b3c-4d5e-8f7a-6b8c0d2e4f1a"
          }
        ],
        "responses": {
          "200": {
            "description": "The operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Operation"
                },
                "example": {
                  "id": "9d2e4f6a-1b3c-4d5e-8f7a-6b8c0d2e4f1a",
                  "boxId": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "type": "exec",
                  "state": "done",
                  "attempt": 1,
                  "progress": null,
                  "result": {
                    "exitCode": 0,
                    "stdout": "Linux box-job-1 6.12.8 #1 SMP x86_64 GNU/Linux\n",
                    "stderr": "",
                    "timedOut": false,
                    "truncated": false
                  },
                  "error": null,
                  "createdAt": "2026-11-05T10:16:00.000Z",
                  "startedAt": "2026-11-05T10:16:00.400Z",
                  "completedAt": "2026-11-05T10:16:00.900Z"
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No operation has this ID in the caller's organizations (an API key's organization only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No operation has this ID in the caller's organizations (an API key's organization only)."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/operations/9d2e4f6a-1b3c-4d5e-8f7a-6b8c0d2e4f1a \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "const operation = await cubicle.operations.get(\"9d2e4f6a-1b3c-4d5e-8f7a-6b8c0d2e4f1a\")\n// Or poll until it ends; wait() rejects when it ends failed or unknown:\n// await cubicle.operations.wait(operation.id)"
          }
        ]
      }
    },
    "/v1/boxes/{box}/snapshots": {
      "post": {
        "operationId": "create-snapshot",
        "summary": "Take a snapshot",
        "description": "Queues a snapshot of a stopped Box's disk and returns its [operation](https://docs.cubicle.sh/api/reference/operations/get-operation/) with `202 Accepted`. The snapshot is listed by [List a Box's snapshots](https://docs.cubicle.sh/api/reference/operations/list-box-snapshots/) at once, with this `operationId`, and its `status` follows the operation. See [Following a request](https://docs.cubicle.sh/api/requests/#following-a-request).\n\nThe caller needs a grant on the Box, or the owner or admin role in its organization. API keys need the `write` scope.\n\n- The Box must be desired `stopped`; stopping a hibernated Box discards its saved memory. Wait until `state` is `stopped` at the current generation: if the Box is still shutting down, the operation can fail with `box must be stopped`.\n- A snapshot holds the Box's whole disk, including every account's home directory, but not its memory. Use its ID, not its name, to restore it or to create Boxes from it.\n- Until the operation ends, starting, stopping, hibernating and deleting the Box return `409 conflict`.\n- Snapshots stay on the Box's Node and are deleted with the Box, never one by one; see [Where snapshots live](https://docs.cubicle.sh/concepts/snapshots/#where-snapshots-live).\n\nWith an `Idempotency-Key`, repeating the request with the same body returns `202` with the operation the first request created. See [Snapshot and restore a Box](https://docs.cubicle.sh/guides/snapshots/).",
        "tags": [
          "Snapshots"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the request safe to retry: repeating it with the same key and body returns the first result instead of doing the work again. 1 to 200 characters; a UUID is a good choice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "4f1c7c1e-6a43-4d5e-9b7e-0d2f6c0e9a51"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SnapshotCreateRequest"
              },
              "example": {
                "name": "before-upgrade"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The snapshot operation: new, or the original one for an `Idempotency-Key` replay.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Operation"
                },
                "example": {
                  "id": "3e5a7c9e-1b3d-4f5a-8c7e-9b1d3f5a7c2e",
                  "boxId": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "type": "snapshot",
                  "state": "pending",
                  "attempt": 0,
                  "progress": null,
                  "result": null,
                  "error": null,
                  "createdAt": "2026-11-05T10:20:00.000Z",
                  "startedAt": null,
                  "completedAt": null
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.\n- `invalid_request`: `Idempotency-Key` is not 1 to 200 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "The body is not valid JSON."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller holds no grant on the Box and is not an owner or admin of its organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `insufficient_scope`: A `read` API key was used; this route needs `write`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller holds no grant on the Box and is not an owner or admin of its organization."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.\n- `conflict`: The Box is not desired `stopped`.\n- `idempotency_conflict`: The `Idempotency-Key` was already used for a snapshot with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/snapshots \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"before-upgrade\"}'"
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "// Waits until the snapshot is complete.\nconst snapshot = await cubicle.boxes.snapshot(\"acme/job-1\", \"before-upgrade\")"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle snapshot acme/job-1 before-upgrade"
          }
        ]
      },
      "get": {
        "operationId": "list-box-snapshots",
        "summary": "List a Box's snapshots",
        "description": "Returns every snapshot of the Box, newest first, as a bare array: pending, complete and failed ones. Any active member of the Box's organization may list them, without a grant.\n\nSee [Snapshots](https://docs.cubicle.sh/concepts/snapshots/).",
        "tags": [
          "Snapshots"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Box's snapshots, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Snapshot"
                  }
                },
                "example": [
                  {
                    "id": "5a7c9e1b-3d5f-4a6b-8c0d-2e4f6a8b0c3d",
                    "name": "before-upgrade",
                    "status": "complete",
                    "operationId": "3e5a7c9e-1b3d-4f5a-8c7e-9b1d3f5a7c2e",
                    "createdAt": "2026-11-05T10:20:00.000Z",
                    "completedAt": "2026-11-05T10:21:12.000Z"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/snapshots \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "const snapshots = await cubicle.boxes.listSnapshots(\"acme/job-1\")"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle snapshots acme/job-1"
          }
        ]
      }
    },
    "/v1/boxes/{box}/restore": {
      "post": {
        "operationId": "restore-box",
        "summary": "Restore a Box from a snapshot",
        "description": "Queues replacing a stopped Box's disk with one of its own complete snapshots, and returns the restore [operation](https://docs.cubicle.sh/api/reference/operations/get-operation/) with `202 Accepted`. Poll the operation until it is `done`, then [start the Box](https://docs.cubicle.sh/api/reference/operations/start-box/): it stays stopped. See [Following a request](https://docs.cubicle.sh/api/requests/#following-a-request).\n\nThe caller needs a grant on the Box, or the owner or admin role in its organization. API keys need the `write` scope.\n\n- The Box must be desired `stopped`, and should be `stopped` at the current generation: otherwise the operation can fail with `box must be stopped`.\n- The snapshot must be this Box's. To use another Box's snapshot, [create a Box](https://docs.cubicle.sh/api/reference/operations/create-box/) from it.\n- The disk is replaced as a whole. The Box keeps its ID, name, private IP, size and grants.\n- `connectionGeneration` advances at once, and guest accounts are applied again from the current grants and SSH keys; see [What a restore changes](https://docs.cubicle.sh/concepts/snapshots/#what-a-restore-changes).\n- Until the operation ends, starting, stopping, hibernating and deleting the Box return `409 conflict`.\n\nWith an `Idempotency-Key`, repeating the request with the same body returns `202` with the operation the first request created.",
        "tags": [
          "Snapshots"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the request safe to retry: repeating it with the same key and body returns the first result instead of doing the work again. 1 to 200 characters; a UUID is a good choice.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "4f1c7c1e-6a43-4d5e-9b7e-0d2f6c0e9a51"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SnapshotRestoreRequest"
              },
              "example": {
                "snapshotId": "5a7c9e1b-3d5f-4a6b-8c0d-2e4f6a8b0c3d"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The restore operation: new, or the original one for an `Idempotency-Key` replay.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Operation"
                },
                "example": {
                  "id": "6c8e0a2c-4e6a-4b8c-9e0a-4c6e8a0c2e5b",
                  "boxId": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "type": "restore",
                  "state": "pending",
                  "attempt": 0,
                  "progress": null,
                  "result": null,
                  "error": null,
                  "createdAt": "2026-11-05T10:30:00.000Z",
                  "startedAt": null,
                  "completedAt": null
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.\n- `invalid_request`: `Idempotency-Key` is not 1 to 200 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "The body is not valid JSON."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller holds no grant on the Box and is not an owner or admin of its organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `insufficient_scope`: A `read` API key was used; this route needs `write`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller holds no grant on the Box and is not an owner or admin of its organization."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted.\n- `not_found`: `snapshotId` is not a snapshot of this Box.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations (an API key's organization only), or the Box has been deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.\n- `conflict`: The Box is not desired `stopped`.\n- `conflict`: The snapshot is not complete.\n- `idempotency_conflict`: The `Idempotency-Key` was already used for a restore with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/restore \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"snapshotId\":\"5a7c9e1b-3d5f-4a6b-8c0d-2e4f6a8b0c3d\"}'"
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "// Waits until the restore is done; the Box stays stopped.\nawait cubicle.boxes.restore(\"acme/job-1\", \"5a7c9e1b-3d5f-4a6b-8c0d-2e4f6a8b0c3d\")"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle restore acme/job-1 5a7c9e1b-3d5f-4a6b-8c0d-2e4f6a8b0c3d"
          }
        ]
      }
    },
    "/v1/orgs/{org}/snapshots": {
      "get": {
        "operationId": "list-org-snapshots",
        "summary": "List snapshots to create Boxes from",
        "description": "Returns the complete snapshots in `{org}` that the caller may [create a Box](https://docs.cubicle.sh/api/reference/operations/create-box/) from, as a bare array sorted by source Box name and then newest first. Owners and admins see every one; members see only snapshots of Boxes they hold a grant on. Snapshots of deleted Boxes are not listed.\n\nEach entry names its source Box, the Node that holds it and the size a Box created from it gets by default. Any active member may call this route; API keys may list only their own organization.\n\nSee [Snapshots](https://docs.cubicle.sh/concepts/snapshots/).",
        "tags": [
          "Snapshots"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's name or ID.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          }
        ],
        "responses": {
          "200": {
            "description": "Snapshots the caller may create Boxes from.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CreationSnapshot"
                  }
                },
                "example": [
                  {
                    "id": "5a7c9e1b-3d5f-4a6b-8c0d-2e4f6a8b0c3d",
                    "name": "before-upgrade",
                    "createdAt": "2026-11-05T10:20:00.000Z",
                    "box": {
                      "id": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                      "name": "job-1"
                    },
                    "node": {
                      "id": "c3a9e1f7-5b2d-4a8c-8e6f-0d1b3c5e7a9f",
                      "name": "node-1"
                    },
                    "spec": {
                      "vcpus": 2,
                      "memoryMiB": 2048,
                      "diskGiB": 12
                    }
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The caller is not an active member of `{org}`, it does not exist, or an API key belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The caller is not an active member of `{org}`, it does not exist, or an API key belongs to another organization."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/orgs/acme/snapshots \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "const snapshots = await cubicle.listSnapshots({ org: \"acme\" })"
          }
        ]
      }
    },
    "/v1/boxes/{box}/grants": {
      "get": {
        "operationId": "list-grants",
        "summary": "List a Box's grants",
        "description": "Returns every grant on the Box, revoked ones included, sorted by identity, as a bare array. Any active member of the Box's organization may list them with a sign-in session; API keys cannot use this route.\n\nEach granted member has their own [guest account](https://docs.cubicle.sh/concepts/access/#guest-accounts) in the Box (`guestAccount`). The Box has applied the latest access change for a member when `appliedRevision` equals `desiredRevision`. [Box grants](https://docs.cubicle.sh/concepts/access/#box-grants) explains what a grant allows.",
        "tags": [
          "Box access"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Box's grants.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "userId": {
                        "type": "string",
                        "description": "The member's user ID: a person's or a service account's."
                      },
                      "identity": {
                        "type": "string",
                        "description": "The member's identity: an email address for a person, `ORG/NAME` for a service account."
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "active",
                          "revoked"
                        ],
                        "description": "`active`, or `revoked` for a grant that was revoked. Revoked grants stay listed."
                      },
                      "guestAccount": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "The member's Unix account in the Box: `jv_` followed by the first 12 hex characters of their user ID."
                      },
                      "desiredRevision": {
                        "type": [
                          "number",
                          "null"
                        ],
                        "description": "The revision of the member's account that the Box should apply. It advances with every access change for the member, such as a new SSH key or a revoked grant."
                      },
                      "appliedRevision": {
                        "type": [
                          "number",
                          "null"
                        ],
                        "description": "The revision the Box has applied. The latest access change is in effect when it equals `desiredRevision`."
                      }
                    },
                    "required": [
                      "userId",
                      "identity",
                      "status",
                      "guestAccount",
                      "desiredRevision",
                      "appliedRevision"
                    ]
                  }
                },
                "example": [
                  {
                    "userId": "2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a",
                    "identity": "ada@example.com",
                    "status": "active",
                    "guestAccount": "jv_2c4e6a8b0d1f",
                    "desiredRevision": 2,
                    "appliedRevision": 2
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations, or the Box is being deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations, or the Box is being deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/grants \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle grants list acme/job-1"
          }
        ]
      }
    },
    "/v1/boxes/{box}/grants/{user}": {
      "put": {
        "operationId": "grant-access",
        "summary": "Grant access to a Box",
        "description": "Gives a member of the Box's organization a grant on the Box, and returns `204 No Content`. Owners and admins may grant any active member, themselves and service accounts included, with a sign-in session; API keys cannot use this route.\n\nThe member gets their [guest account](https://docs.cubicle.sh/concepts/access/#guest-accounts) in the Box, at once if the Box is running, or when it next starts. Granting a member who already has an active grant changes nothing but applies their account again. A grant is what allows running commands, SSH, private-network access and the console; the owner or admin role alone does not. See [Box grants](https://docs.cubicle.sh/concepts/access/#box-grants).",
        "tags": [
          "Box access"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "user",
            "in": "path",
            "description": "The member's user ID: a person's, or a service account's. [List members](https://docs.cubicle.sh/api/reference/operations/list-members/) shows them.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The member holds an active grant on the Box."
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_request`: `{user}` is not a UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "`{user}` is not a UUID."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller is not an owner or admin of the Box's organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller is not an owner or admin of the Box's organization."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations, or the Box is being deleted.\n- `not_found`: `{user}` is not an active member of the Box's organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations, or the Box is being deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X PUT https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/grants/2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle grants add acme/job-1 2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a"
          }
        ]
      },
      "delete": {
        "operationId": "revoke-access",
        "summary": "Revoke access to a Box",
        "description": "Revokes a member's grant on the Box and returns `204 No Content`. Any member may revoke their own grant; revoking someone else's needs the owner or admin role. The route needs a sign-in session; API keys cannot use it.\n\nThe member's exec, SSH, private-network and console access to the Box ends, including connections already open, and without the owner or admin role they can no longer manage it. Their guest account is locked and loses its SSH keys, at once if the Box is running or when it next starts; its home directory stays on the disk. The grant stays listed with `status: \"revoked\"` and can be granted again. See [Revoking access](https://docs.cubicle.sh/concepts/access/#revoking-access).",
        "tags": [
          "Box access"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "user",
            "in": "path",
            "description": "The member's user ID: a person's, or a service account's. [List members](https://docs.cubicle.sh/api/reference/operations/list-members/) shows them.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The grant is revoked."
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_request`: `{user}` is not a UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "`{user}` is not a UUID."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: `{user}` is not the caller, and the caller is not an owner or admin of the Box's organization.\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "`{user}` is not the caller, and the caller is not an owner or admin of the Box's organization."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations, or the Box is being deleted.\n- `not_found`: `{user}` holds no active grant on the Box.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations, or the Box is being deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X DELETE https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/grants/2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle grants rm acme/job-1 2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a"
          }
        ]
      }
    },
    "/v1/boxes/{box}/ssh": {
      "get": {
        "operationId": "get-ssh-details",
        "summary": "Get SSH details for a Box",
        "description": "Returns what the caller needs to SSH into the Box: their guest account (the SSH user), the Box's SSH host key and the alias to pin it under, and whether the caller can connect now. Any active member of the Box's organization may read it with a sign-in session; `granted` says whether the caller holds a grant. API keys cannot use this route.\n\nSSH goes over Cubicle's private network to port 22 on the Box, with the caller's registered SSH key. `hostKey` is `null` until the Box has published a host key for its current `connectionGeneration`; trust only the key returned here. When `ready` is `false`, `reason` names the first unmet condition.\n\n`cubicle ssh-config` reads this route and writes an SSH configuration that pins the host key. See [SSH and host-key pinning](https://docs.cubicle.sh/concepts/networking/#ssh-and-host-key-pinning) and [SSH into a Box](https://docs.cubicle.sh/guides/ssh/).",
        "tags": [
          "Box access"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "box",
            "in": "path",
            "description": "The Box's ID, `ORG/NAME` with the slash encoded as `%2F`, or a bare `NAME`, which `?org=` narrows. See [Box references](https://docs.cubicle.sh/api/#box-and-organization-references).",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme%2Fjob-1"
          },
          {
            "name": "org",
            "in": "query",
            "description": "The organization's ID or name, to narrow a bare `NAME` in `{box}`. Ignored for an ID or `ORG/NAME`.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's SSH details for the Box.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SshInfo"
                },
                "example": {
                  "boxId": "0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "granted": true,
                  "guestAccount": "jv_2c4e6a8b0d1f",
                  "hostKeyAlias": "jevbox-0f6c2a5e-3b1d-4c8e-9a7f-2d4e6b8c1a3f",
                  "hostKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK5mFq0dXx3TQ2vC9yH8rLwP4jN7sBzE1aG6uM2kR0tY",
                  "connectionGeneration": 1,
                  "ready": true,
                  "reason": null
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: No Box matches `{box}` in the caller's organizations, or the Box is being deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No Box matches `{box}` in the caller's organizations, or the Box is being deleted."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `ambiguous_box`: A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "ambiguous_box",
                    "message": "A bare `NAME` matches Boxes in several of the caller's organizations; use the ID, `ORG/NAME` or `?org=`."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/boxes/acme%2Fjob-1/ssh \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle ssh-config acme/job-1"
          }
        ]
      }
    },
    "/v1/orgs/{org}/members": {
      "get": {
        "operationId": "list-members",
        "summary": "List an organization's members",
        "description": "Returns everyone who holds or has held a membership in the organization, people and service accounts alike, ordered by identity.\n\n- Removed members stay in the list with `status: \"revoked\"`.\n- A deleted service account keeps its row with `status: \"revoked\"`, and its identity gains `#` and the first 8 characters of its ID (`acme/ci#8e0a2c4e`).\n\nAny active member of the organization may call this route, whatever their role. It needs a sign-in session; API keys are refused.\n\nSee [Organizations and access](https://docs.cubicle.sh/concepts/access/) for what each role may do.",
        "tags": [
          "Organizations"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's ID or name. An organization where the caller is not an active member returns `404`.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          }
        ],
        "responses": {
          "200": {
            "description": "The organization's members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "The user's or service account's ID."
                      },
                      "identity": {
                        "type": "string",
                        "description": "The person's sign-in identity, usually an email address, or `ORG/NAME` for a service account."
                      },
                      "role": {
                        "$ref": "#/components/schemas/Role",
                        "description": "The member's role in the organization."
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "active",
                          "revoked"
                        ],
                        "description": "`revoked` once the member was removed or the service account deleted."
                      },
                      "serviceAccount": {
                        "type": "boolean",
                        "description": "Whether the member is one of the organization's service accounts."
                      }
                    },
                    "required": [
                      "id",
                      "identity",
                      "role",
                      "status",
                      "serviceAccount"
                    ]
                  }
                },
                "example": [
                  {
                    "id": "8e0a2c4e-6b8d-4f1a-a3c5-9e1b3d5f7a9c",
                    "identity": "acme/ci",
                    "role": "member",
                    "status": "active",
                    "serviceAccount": true
                  },
                  {
                    "id": "2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a",
                    "identity": "ada@example.com",
                    "role": "admin",
                    "status": "active",
                    "serviceAccount": false
                  },
                  {
                    "id": "9a1c3e5f-7b9d-4e2a-8c4f-6d8b0a2c4e7f",
                    "identity": "grace@example.com",
                    "role": "owner",
                    "status": "active",
                    "serviceAccount": false
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The organization does not exist, or the caller is not an active member of it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The organization does not exist, or the caller is not an active member of it."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/orgs/acme/members \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle members list --org acme"
          }
        ]
      },
      "post": {
        "operationId": "add-member",
        "summary": "Add a member to an organization",
        "description": "Gives an existing Cubicle user a role in the organization. The user is found by `identity`, matched exactly: case-sensitive and not trimmed. This route cannot invite someone who has no account.\n\nThe route writes the membership whether or not one exists:\n\n- A user who is not a member becomes one with `role`.\n- An active member's role is set to `role`, higher or lower than before. `role` defaults to `member` when omitted.\n- A removed member becomes active again with `role`, and the Box grants they held before apply again.\n\nOwners and admins may call this route. Setting `role: \"owner\"` needs the owner role; an admin may still set an owner's role to `admin` or `member`. It needs a sign-in session; API keys are refused.\n\nService accounts hold only `member` or `admin`, and only in their own organization; create them with [Create a service account](https://docs.cubicle.sh/api/reference/operations/create-service-account/). See [Change a member's role](https://docs.cubicle.sh/guides/members/#change-a-members-role) before you lower an owner's role.",
        "tags": [
          "Organizations"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's ID or name. An organization where the caller is not an active member returns `404`.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemberCreateRequest"
              },
              "example": {
                "identity": "ada@example.com",
                "role": "admin"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The membership as written, for a new or an existing member alike.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The user's ID."
                    },
                    "identity": {
                      "type": "string",
                      "description": "The `identity` from the request."
                    },
                    "role": {
                      "$ref": "#/components/schemas/Role",
                      "description": "The role the membership now has."
                    }
                  },
                  "required": [
                    "id",
                    "identity",
                    "role"
                  ]
                },
                "example": {
                  "id": "2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a",
                  "identity": "ada@example.com",
                  "role": "admin"
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_request`: The user is a service account of another organization, or `role` is `owner` for a service account.\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "The user is a service account of another organization, or `role` is `owner` for a service account."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller is a member of the organization but neither an owner nor an admin.\n- `forbidden`: `role` is `owner` and the caller is not an owner.\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller is a member of the organization but neither an owner nor an admin."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The organization does not exist, or the caller is not an active member of it.\n- `not_found`: No user has exactly this `identity`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The organization does not exist, or the caller is not an active member of it."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/orgs/acme/members \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"identity\":\"ada@example.com\",\"role\":\"admin\"}'"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle members add --org acme ada@example.com --role admin"
          }
        ]
      }
    },
    "/v1/orgs/{org}/members/{user}": {
      "delete": {
        "operationId": "remove-member",
        "summary": "Remove a member from an organization",
        "description": "Ends a person's membership: they lose access to the organization and its Boxes. Their Box grants stay on record but stop working; they apply again if the person is [added back](https://docs.cubicle.sh/api/reference/operations/add-member/).\n\nIf `{user}` is one of the organization's service accounts, the route deletes it, exactly like [Delete a service account](https://docs.cubicle.sh/api/reference/operations/delete-service-account/).\n\nOwners and admins may call this route. They may remove any member, owners and themselves included, except the organization's last active owner. It needs a sign-in session; API keys are refused.",
        "tags": [
          "Organizations"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's ID or name. An organization where the caller is not an active member returns `404`.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          },
          {
            "name": "user",
            "in": "path",
            "description": "The member's user ID, a UUID; identities are not accepted. [List members](https://docs.cubicle.sh/api/reference/operations/list-members/) returns it as `id`.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a"
          }
        ],
        "responses": {
          "204": {
            "description": "The membership is revoked."
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_request`: `{user}` is not a UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "`{user}` is not a UUID."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller is a member of the organization but neither an owner nor an admin.\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller is a member of the organization but neither an owner nor an admin."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The organization does not exist, or the caller is not an active member of it.\n- `not_found`: `{user}` has no active membership in the organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The organization does not exist, or the caller is not an active member of it."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `last_owner`: `{user}` is the organization's only active owner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "last_owner",
                    "message": "`{user}` is the organization's only active owner."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X DELETE https://api.cubicle.sh/v1/orgs/acme/members/2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle members rm --org acme 2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a"
          }
        ]
      }
    },
    "/v1/orgs/{org}/service-accounts": {
      "get": {
        "operationId": "list-service-accounts",
        "summary": "List service accounts",
        "description": "Returns the organization's service accounts, ordered by identity, each with its API keys, oldest key first. Deleted service accounts and revoked keys are left out. Tokens are never returned; `hint` identifies each key.\n\nA key past its `expiresAt` stays in the list until it is revoked, but it no longer authenticates.\n\nOnly the organization's owners and admins may call this route, with a sign-in session; API keys are refused.\n\nSee [Run CI jobs in Boxes](https://docs.cubicle.sh/use-cases/ci/).",
        "tags": [
          "Service accounts"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's ID or name. An organization where the caller is not an active member returns `404`.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          }
        ],
        "responses": {
          "200": {
            "description": "The organization's service accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ServiceAccount"
                  }
                },
                "example": [
                  {
                    "id": "8e0a2c4e-6b8d-4f1a-a3c5-9e1b3d5f7a9c",
                    "name": "ci",
                    "identity": "acme/ci",
                    "role": "member",
                    "createdAt": "2026-11-01T09:00:00.000Z",
                    "keys": [
                      {
                        "id": "4b6d8f0a-2c4e-4a6b-8d0f-1a3c5e7b9d2f",
                        "name": "github-actions",
                        "scope": "write",
                        "hint": "cbk_2QwBcGYt",
                        "createdBy": {
                          "id": "2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a",
                          "identity": "ada@example.com"
                        },
                        "createdAt": "2026-11-01T09:05:00.000Z",
                        "expiresAt": "2027-01-30T09:05:00.000Z",
                        "lastUsedAt": "2026-11-05T10:14:21.000Z"
                      }
                    ]
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller is a member of the organization but neither an owner nor an admin.\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller is a member of the organization but neither an owner nor an admin."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The organization does not exist, or the caller is not an active member of it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The organization does not exist, or the caller is not an active member of it."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/orgs/acme/service-accounts \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle service-accounts list --org acme"
          }
        ]
      },
      "post": {
        "operationId": "create-service-account",
        "summary": "Create a service account",
        "description": "Creates a non-interactive member of the organization for automation, such as CI. Its identity is `ORG/NAME` (`acme/ci`). It never signs in, and it starts with no API keys and no Box grants.\n\n- `name` must be unique among the organization's live service accounts; a deleted account's name can be used again. Avoid UUID-shaped names: `{account}` reads a UUID as an ID.\n- `role` is `member` (the default) or `admin`, never `owner`; [Service accounts](https://docs.cubicle.sh/concepts/access/#service-accounts) says what each allows. Running commands always needs a grant.\n\nNext, [create an API key](https://docs.cubicle.sh/api/reference/operations/create-api-key/) for it and [grant it access](https://docs.cubicle.sh/api/reference/operations/grant-access/) to the Boxes it should use. A Box it creates itself is granted to it automatically.\n\nOnly the organization's owners and admins may call this route, with a sign-in session; API keys are refused. See [Run CI jobs in Boxes](https://docs.cubicle.sh/use-cases/ci/).",
        "tags": [
          "Service accounts"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's ID or name. An organization where the caller is not an active member returns `404`.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceAccountCreateRequest"
              },
              "example": {
                "name": "ci",
                "role": "member"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new service account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceAccount"
                },
                "example": {
                  "id": "8e0a2c4e-6b8d-4f1a-a3c5-9e1b3d5f7a9c",
                  "name": "ci",
                  "identity": "acme/ci",
                  "role": "member",
                  "createdAt": "2026-11-01T09:00:00.000Z",
                  "keys": []
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "The body is not valid JSON."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller is a member of the organization but neither an owner nor an admin.\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller is a member of the organization but neither an owner nor an admin."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The organization does not exist, or the caller is not an active member of it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The organization does not exist, or the caller is not an active member of it."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `conflict`: The organization already has a live service account with this `name`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The organization already has a live service account with this `name`."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/orgs/acme/service-accounts \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"ci\",\"role\":\"member\"}'"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle service-accounts create --org acme ci --role member"
          }
        ]
      }
    },
    "/v1/orgs/{org}/service-accounts/{account}": {
      "delete": {
        "operationId": "delete-service-account",
        "summary": "Delete a service account",
        "description": "Ends the service account at once:\n\n- Its membership is revoked, so its Box grants stop working.\n- Every API key it holds is revoked; requests with them fail with `401 unauthenticated`.\n- Its name is freed for a new service account. The old account stays on record under a changed identity, as [List members](https://docs.cubicle.sh/api/reference/operations/list-members/) shows.\n\nBoxes it created stay in the organization. [Remove a member](https://docs.cubicle.sh/api/reference/operations/remove-member/) with the service account's ID does the same as this route.\n\nOnly the organization's owners and admins may call this route, with a sign-in session; API keys are refused.",
        "tags": [
          "Service accounts"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's ID or name. An organization where the caller is not an active member returns `404`.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          },
          {
            "name": "account",
            "in": "path",
            "description": "The service account's ID, or its `NAME` without the organization (`ci` for `acme/ci`). A UUID is always read as an ID.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "ci"
          }
        ],
        "responses": {
          "204": {
            "description": "The service account is deleted."
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller is a member of the organization but neither an owner nor an admin.\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller is a member of the organization but neither an owner nor an admin."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The organization does not exist, or the caller is not an active member of it.\n- `not_found`: The organization has no live service account matching `{account}`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The organization does not exist, or the caller is not an active member of it."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X DELETE https://api.cubicle.sh/v1/orgs/acme/service-accounts/ci \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle service-accounts rm --org acme ci"
          }
        ]
      }
    },
    "/v1/orgs/{org}/service-accounts/{account}/keys": {
      "post": {
        "operationId": "create-api-key",
        "summary": "Create an API key",
        "description": "Issues an API key for the service account and returns its secret `token` (`cbk_…`). The token is in this response only; listings show only `hint`, its first 12 characters (see [The key is shown once](https://docs.cubicle.sh/authentication/#the-key-is-shown-once)). Send it directly as `Authorization: Bearer cbk_…`.\n\n- `name` must be unique among the service account's keys that are not revoked, expired ones included.\n- `scope` is `read` or `write`. A `read` key may call only `GET` routes; a `write` key may call every [route that accepts API keys](https://docs.cubicle.sh/api/authentication/#routes-that-accept-api-keys). Either way the key acts as its service account, only inside this organization.\n- `expiresInDays`: the key stops working that many days after creation. Without it the key never expires.\n\nThe key stops working when it is revoked or expires, or when its service account is deleted, not when the person who created it leaves the organization.\n\nOnly the organization's owners and admins may call this route, with a sign-in session; API keys are refused.",
        "tags": [
          "Service accounts"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's ID or name. An organization where the caller is not an active member returns `404`.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          },
          {
            "name": "account",
            "in": "path",
            "description": "The service account's ID, or its `NAME` without the organization (`ci` for `acme/ci`). A UUID is always read as an ID.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "ci"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApiKeyCreateRequest"
              },
              "example": {
                "name": "github-actions",
                "scope": "write",
                "expiresInDays": 90
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new key with its token. Store the token now; it is not shown again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedApiKey"
                },
                "example": {
                  "id": "4b6d8f0a-2c4e-4a6b-8d0f-1a3c5e7b9d2f",
                  "name": "github-actions",
                  "scope": "write",
                  "hint": "cbk_2QwBcGYt",
                  "createdBy": {
                    "id": "2c4e6a8b-0d1f-4e3a-9b5c-7d9f1b3d5e7a",
                    "identity": "ada@example.com"
                  },
                  "createdAt": "2026-11-01T09:05:00.000Z",
                  "expiresAt": "2027-01-30T09:05:00.000Z",
                  "lastUsedAt": null,
                  "token": "cbk_2QwBcGYt9L6RRRbIV5SxgBf2W3WyiJMDUyq5tuYTW0M"
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_json",
                    "message": "The body is not valid JSON."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller is a member of the organization but neither an owner nor an admin.\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller is a member of the organization but neither an owner nor an admin."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The organization does not exist, or the caller is not an active member of it.\n- `not_found`: The organization has no live service account matching `{account}`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The organization does not exist, or the caller is not an active member of it."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `conflict`: The service account already has a key with this `name` that is not revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The service account already has a key with this `name` that is not revoked."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/orgs/acme/service-accounts/ci/keys \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"github-actions\",\"scope\":\"write\",\"expiresInDays\":90}'"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle api-keys create --org acme ci github-actions --scope write --expires-days 90"
          }
        ]
      }
    },
    "/v1/orgs/{org}/service-accounts/{account}/keys/{key}": {
      "delete": {
        "operationId": "revoke-api-key",
        "summary": "Revoke an API key",
        "description": "Revokes the key at once: requests that use it fail with `401 unauthenticated` from then on. A revoked key cannot be restored; create a new one instead. It no longer appears in [List service accounts](https://docs.cubicle.sh/api/reference/operations/list-service-accounts/), and its name can be used again.\n\nOnly the organization's owners and admins may call this route, with a sign-in session; API keys are refused.",
        "tags": [
          "Service accounts"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "org",
            "in": "path",
            "description": "The organization's ID or name. An organization where the caller is not an active member returns `404`.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          },
          {
            "name": "account",
            "in": "path",
            "description": "The service account's ID, or its `NAME` without the organization (`ci` for `acme/ci`). A UUID is always read as an ID.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "ci"
          },
          {
            "name": "key",
            "in": "path",
            "description": "The API key's ID, a UUID.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "4b6d8f0a-2c4e-4a6b-8d0f-1a3c5e7b9d2f"
          }
        ],
        "responses": {
          "204": {
            "description": "The key is revoked."
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_request`: `{key}` is not a UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "`{key}` is not a UUID."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `forbidden`: The caller is a member of the organization but neither an owner nor an admin.\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The caller is a member of the organization but neither an owner nor an admin."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The organization does not exist, or the caller is not an active member of it.\n- `not_found`: The organization has no live service account matching `{account}`.\n- `not_found`: The service account has no unrevoked key with this ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The organization does not exist, or the caller is not an active member of it."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X DELETE https://api.cubicle.sh/v1/orgs/acme/service-accounts/ci/keys/4b6d8f0a-2c4e-4a6b-8d0f-1a3c5e7b9d2f \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle api-keys revoke --org acme ci 4b6d8f0a-2c4e-4a6b-8d0f-1a3c5e7b9d2f"
          }
        ]
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "get-me",
        "summary": "Get the caller",
        "description": "Returns who the bearer token belongs to and the organizations it can use. API keys of either scope and sign-in sessions may call this route.\n\n- **With an API key**, `id` and `identity` are the key's service account (`acme/ci`), `orgs` holds only the key's organization, and `apiKey` gives the key's ID, organization and scope.\n- **With a sign-in session**, `orgs` includes the person's personal organization (`personal: true`), and `apiKey` is absent.\n\nSee [Organizations and access](https://docs.cubicle.sh/concepts/access/).",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller. The example is for an API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                },
                "example": {
                  "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"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/me \\\n  -H \"Authorization: Bearer $CB_SDK_API_KEY\""
          },
          {
            "label": "TypeScript SDK",
            "lang": "ts",
            "source": "const me = await cubicle.me()\n// => { identity: \"acme/ci\", orgs: [{ name: \"acme\", … }], apiKey: { scope: \"write\", … }, … }"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle whoami"
          }
        ]
      }
    },
    "/v1/sessions": {
      "get": {
        "operationId": "list-sessions",
        "summary": "List your sessions",
        "description": "Returns the caller's live sign-in sessions, most recently used first: Dashboard sign-ins (`client: \"dashboard\"`) and CLI logins (`client: \"cli\"`). Revoked and expired sessions are left out, and tokens are never returned. `current` marks the session that made this request.\n\nA session ends at `expiresAt`, 30 days after sign-in. End one early with [Revoke a session](https://docs.cubicle.sh/api/reference/operations/revoke-session/).\n\nIt needs a sign-in session; API keys are refused.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's live sessions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SessionInfo"
                  }
                },
                "example": [
                  {
                    "id": "7f9b1d3f-5a7c-4e9b-b1d3-3f5a7c9e1b4d",
                    "client": "cli",
                    "createdAt": "2026-11-03T16:40:12.000Z",
                    "lastUsedAt": "2026-11-05T10:14:21.000Z",
                    "expiresAt": "2026-12-03T16:40:12.000Z",
                    "current": true
                  },
                  {
                    "id": "6e8a0c2e-4b6d-4f8a-a0c2-2e4b6d8f0a3c",
                    "client": "dashboard",
                    "createdAt": "2026-11-01T08:55:40.000Z",
                    "lastUsedAt": "2026-11-04T17:02:09.000Z",
                    "expiresAt": "2026-12-01T08:55:40.000Z",
                    "current": false
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/sessions \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle sessions list"
          }
        ]
      }
    },
    "/v1/sessions/{session}": {
      "delete": {
        "operationId": "revoke-session",
        "summary": "Revoke a session",
        "description": "Ends one of the caller's own sessions at once, such as a CLI login on a lost laptop. Requests with its token then fail with `401 unauthenticated`.\n\nRevoking the session that makes this request (`current: true`) signs the caller out, like [Sign out](https://docs.cubicle.sh/api/reference/operations/sign-out/). Other sessions are not affected.\n\nIt needs a sign-in session; API keys are refused. A caller can revoke only their own sessions.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "session",
            "in": "path",
            "description": "The session's ID, a UUID, from [List your sessions](https://docs.cubicle.sh/api/reference/operations/list-sessions/).",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "7f9b1d3f-5a7c-4e9b-b1d3-3f5a7c9e1b4d"
          }
        ],
        "responses": {
          "204": {
            "description": "The session is revoked."
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_request`: `{session}` is not a UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "`{session}` is not a UUID."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: None of the caller's unrevoked sessions has this ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "None of the caller's unrevoked sessions has this ID."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X DELETE https://api.cubicle.sh/v1/sessions/7f9b1d3f-5a7c-4e9b-b1d3-3f5a7c9e1b4d \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle sessions revoke 7f9b1d3f-5a7c-4e9b-b1d3-3f5a7c9e1b4d"
          }
        ]
      }
    },
    "/v1/keys": {
      "get": {
        "operationId": "list-ssh-keys",
        "summary": "List your SSH keys",
        "description": "Returns the caller's SSH public keys, oldest first. Removed keys are left out.\n\nEvery key here is authorized for the caller's guest account on each Box they have a grant for. See [SSH into a Box](https://docs.cubicle.sh/guides/ssh/).\n\nIt needs a sign-in session; API keys are refused.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's SSH keys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SshKey"
                  }
                },
                "example": [
                  {
                    "id": "1d3f5b7d-9f1b-4c3d-a5f7-5b7d9f1a3c6e",
                    "fingerprint": "SHA256:DR+VtH2CFcmbwFuSvbFDPP8Twxmp78l6+a+IUVna6C4",
                    "comment": "ada@laptop",
                    "publicKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIq5sz0buTLsmQ/57RQ86GwP2iIh5VrYHXVPY4ACn8xH",
                    "createdAt": "2026-11-02T08:30:00.000Z"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl https://api.cubicle.sh/v1/keys \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle keys list"
          }
        ]
      },
      "post": {
        "operationId": "add-ssh-key",
        "summary": "Add an SSH key",
        "description": "Adds an SSH public key to the caller's account. It is then authorized for the caller's guest account on every Box they have a grant for, now and later.\n\n- Only `ssh-ed25519` keys are accepted. Send one line in OpenSSH format, as in `~/.ssh/id_ed25519.pub`: `ssh-ed25519`, the base64 key and an optional comment.\n- The key is stored as `ssh-ed25519` and the base64 key. The rest of the line becomes its `comment`, cut to 200 characters.\n\n[List your SSH keys](https://docs.cubicle.sh/api/reference/operations/list-ssh-keys/) returns the full keys.\n\nIt needs a sign-in session; API keys are refused. See [SSH into a Box](https://docs.cubicle.sh/guides/ssh/).",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SshKeyCreateRequest"
              },
              "example": {
                "publicKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIq5sz0buTLsmQ/57RQ86GwP2iIh5VrYHXVPY4ACn8xH ada@laptop"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The key is added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The key's ID, used to remove it."
                    },
                    "fingerprint": {
                      "type": "string",
                      "description": "The key's SHA-256 fingerprint, as `ssh-keygen -l` prints it (`SHA256:…`)."
                    },
                    "createdAt": {
                      "type": "string",
                      "description": "When the key was added (ISO 8601, UTC)."
                    }
                  },
                  "required": [
                    "id",
                    "fingerprint",
                    "createdAt"
                  ]
                },
                "example": {
                  "id": "1d3f5b7d-9f1b-4c3d-a5f7-5b7d9f1a3c6e",
                  "fingerprint": "SHA256:DR+VtH2CFcmbwFuSvbFDPP8Twxmp78l6+a+IUVna6C4",
                  "createdAt": "2026-11-02T08:30:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_key`: The key type is not `ssh-ed25519`, or the base64 key is missing.\n- `invalid_key`: The base64 key does not decode to an Ed25519 public key.\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_key",
                    "message": "The key type is not `ssh-ed25519`, or the base64 key is missing."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the resource's current state.\n\n- `conflict`: The caller already has this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The caller already has this key."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/keys \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"publicKey\":\"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIq5sz0buTLsmQ/57RQ86GwP2iIh5VrYHXVPY4ACn8xH ada@laptop\"}'"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle keys add ~/.ssh/id_ed25519.pub"
          }
        ]
      }
    },
    "/v1/keys/{key}": {
      "delete": {
        "operationId": "remove-ssh-key",
        "summary": "Remove an SSH key",
        "description": "Removes one of the caller's SSH keys. It stops being authorized for the caller's guest account on their Boxes.\n\nIt needs a sign-in session; API keys are refused.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "description": "The SSH key's ID, a UUID, from [List your SSH keys](https://docs.cubicle.sh/api/reference/operations/list-ssh-keys/).",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "1d3f5b7d-9f1b-4c3d-a5f7-5b7d9f1a3c6e"
          }
        ],
        "responses": {
          "204": {
            "description": "The key is removed."
          },
          "400": {
            "description": "The request is invalid.\n\n- `invalid_request`: `{key}` is not a UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "invalid_request",
                    "message": "`{key}` is not a UUID."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist or is not visible to the caller.\n\n- `not_found`: The caller has no SSH key with this ID, or it was already removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "The caller has no SSH key with this ID, or it was already removed."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X DELETE https://api.cubicle.sh/v1/keys/1d3f5b7d-9f1b-4c3d-a5f7-5b7d9f1a3c6e \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle keys rm 1d3f5b7d-9f1b-4c3d-a5f7-5b7d9f1a3c6e"
          }
        ]
      }
    },
    "/v1/auth/device": {
      "post": {
        "operationId": "start-device-sign-in",
        "summary": "Start a device sign-in",
        "description": "Starts the [OAuth 2.0 device authorization flow (RFC 8628)](https://www.rfc-editor.org/rfc/rfc8628) that `cubicle login` uses, for clients that cannot receive a browser redirect: nothing calls back into the client, which only polls. It needs no credentials and no body, and each call starts a separate sign-in with new codes.\n\n1. Call this route. It returns a secret `deviceCode` for the client and a short `userCode` (`XXXX-XXXX`) for the person.\n2. Send the person to `verificationUriComplete`, the Dashboard's `/device` page with the code filled in, and show `userCode` so they can check that it matches. They sign in if needed and choose **Authorize** or **Deny**.\n3. Meanwhile, call [Exchange a device code for a session](https://docs.cubicle.sh/api/reference/operations/get-device-token/) with `deviceCode` every `interval` seconds (2), until it returns a session or fails, for at most `expiresIn` seconds (600).\n\nThe session belongs to the person who chose **Authorize**, whoever started the flow. Calls are rate limited per client address and across all clients before anything else runs; see [Rate limits](https://docs.cubicle.sh/api/rate-limits/). [Sign people in from your tool](https://docs.cubicle.sh/api/authentication/#sign-people-in-from-your-tool) walks through the flow.",
        "tags": [
          "Sign-in"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The sign-in has started.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deviceCode": {
                      "type": "string",
                      "description": "The secret code the client polls with (`jvd_…`). Keep it secret: whoever holds it receives the session."
                    },
                    "userCode": {
                      "type": "string",
                      "pattern": "^[BCDFGHJKLMNPQRSTVWXZ]{4}-[BCDFGHJKLMNPQRSTVWXZ]{4}$",
                      "description": "The code the person checks in the Dashboard: 8 consonants as `XXXX-XXXX`."
                    },
                    "expiresIn": {
                      "type": "integer",
                      "description": "Seconds until the codes expire: 600."
                    },
                    "interval": {
                      "type": "integer",
                      "description": "Seconds to wait between polls: 2."
                    },
                    "verificationUri": {
                      "type": "string",
                      "format": "uri",
                      "description": "The Dashboard's device page, without the code."
                    },
                    "verificationUriComplete": {
                      "type": "string",
                      "format": "uri",
                      "description": "The Dashboard's device page with `userCode` filled in. Send the person here."
                    }
                  },
                  "required": [
                    "deviceCode",
                    "userCode",
                    "expiresIn",
                    "interval",
                    "verificationUri",
                    "verificationUriComplete"
                  ]
                },
                "example": {
                  "deviceCode": "jvd_qRbPpKtAPzyMvWuZ4Pp_3fQxlO0lB7qDcE9E2MpjzJM",
                  "userCode": "BCDF-GHJK",
                  "expiresIn": 600,
                  "interval": 2,
                  "verificationUri": "https://app.cubicle.sh/device",
                  "verificationUriComplete": "https://app.cubicle.sh/device?user_code=BCDF-GHJK"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: Too many sign-in attempts from this client address or from all clients; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many sign-in attempts from this client address or from all clients; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/auth/device"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle login"
          }
        ]
      }
    },
    "/v1/auth/device/token": {
      "post": {
        "operationId": "get-device-token",
        "summary": "Exchange a device code for a session",
        "description": "Polls a sign-in started with [Start a device sign-in](https://docs.cubicle.sh/api/reference/operations/start-device-sign-in/). It needs no credentials: `deviceCode` is the secret.\n\nCall it every `interval` seconds (2). Until the sign-in completes it answers `400` with one of the `error.code` values listed below: keep polling on `authorization_pending`, and stop on the others.\n\nAfter the person chooses **Authorize**, the next poll returns a new session (`jvs_…`) for that person and uses up the code, so later polls return `invalid_grant`. The session shows as `client: \"cli\"` in [List your sessions](https://docs.cubicle.sh/api/reference/operations/list-sessions/). Send `token` as `Authorization: Bearer jvs_…` until `expiresAt`, 30 days later.\n\nPolls are rate limited per client address and across all clients: a refused poll gets `429 rate_limited`, and the client should wait `Retry-After` seconds. See [Sign people in from your tool](https://docs.cubicle.sh/api/authentication/#sign-people-in-from-your-tool) and [Rate limits](https://docs.cubicle.sh/api/rate-limits/).",
        "tags": [
          "Sign-in"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceTokenRequest"
              },
              "example": {
                "deviceCode": "jvd_qRbPpKtAPzyMvWuZ4Pp_3fQxlO0lB7qDcE9E2MpjzJM"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The person authorized the code; this is their new session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiSession"
                },
                "example": {
                  "token": "jvs_lhxa_0pvIYcKCzNEyglwfzH8TbnsmXmRVcCTJ1xaQr8",
                  "identity": "ada@example.com",
                  "expiresAt": "2026-12-05T10:16:40.000Z"
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.\n\n- `authorization_pending`: Nobody has authorized or denied the code yet; poll again after `interval` seconds.\n- `access_denied`: The person denied the sign-in in the Dashboard; stop polling.\n- `expired_token`: The code expired before it was exchanged; start a new sign-in.\n- `invalid_grant`: The code is unknown or was already exchanged for a session.\n- `invalid_json`: The body is not valid JSON.\n- `invalid_request`: The body or a parameter fails validation; `message` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "authorization_pending",
                    "message": "Nobody has authorized or denied the code yet; poll again after `interval` seconds."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 64 KiB.\n\n- `payload_too_large`: The body is larger than 64 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "payload_too_large",
                    "message": "The body is larger than 64 KiB."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: Too many polls from this client address or from all clients; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many polls from this client address or from all clients; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          },
          "503": {
            "description": "The service is unavailable.\n\n- `logins_paused`: Sign-in is paused for now; try again later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "logins_paused",
                    "message": "Sign-in is paused for now; try again later."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/auth/device/token \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"deviceCode\":\"jvd_qRbPpKtAPzyMvWuZ4Pp_3fQxlO0lB7qDcE9E2MpjzJM\"}'"
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle login"
          }
        ]
      }
    },
    "/v1/auth/logout": {
      "post": {
        "operationId": "sign-out",
        "summary": "Sign out",
        "description": "Revokes the session whose token made the request. Later requests with that token fail with `401 unauthenticated`. The person's other sessions stay signed in; end them with [Revoke a session](https://docs.cubicle.sh/api/reference/operations/revoke-session/).\n\nIt needs a sign-in session; API keys are refused. To stop an API key, [revoke it](https://docs.cubicle.sh/api/reference/operations/revoke-api-key/).",
        "tags": [
          "Sign-in"
        ],
        "security": [
          {
            "session": []
          }
        ],
        "responses": {
          "204": {
            "description": "The session is revoked."
          },
          "401": {
            "description": "The credentials are missing, invalid or expired.\n\n- `unauthenticated`: The bearer token is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The bearer token is missing, invalid, expired or revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not do this.\n\n- `blocked`: The client address is blocked after repeated refusals.\n- `api_key_not_allowed`: An API key was used; this route needs a sign-in session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "blocked",
                    "message": "The client address is blocked after repeated refusals."
                  }
                }
              }
            }
          },
          "429": {
            "description": "The caller sent too many requests.\n\n- `rate_limited`: The caller's request budget is spent; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "The caller's request budget is spent; retry after `Retry-After` seconds."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Cubicle failed unexpectedly.\n\n- `internal`: Cubicle failed unexpectedly; the request may be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Cubicle failed unexpectedly; the request may be retried."
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "bash",
            "source": "curl -X POST https://api.cubicle.sh/v1/auth/logout \\\n  -H \"Authorization: Bearer $CUBICLE_SESSION_TOKEN\""
          },
          {
            "label": "CLI",
            "lang": "bash",
            "source": "cubicle logout"
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "description": "Every error has this body. `code` is stable; `message` is for people.",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "A stable, machine-readable error code."
              },
              "message": {
                "type": "string",
                "description": "A human-readable explanation."
              },
              "retryAfterSeconds": {
                "type": "integer",
                "description": "Present on `429 rate_limited` and `403 blocked`; also sent as `Retry-After`."
              }
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "error"
        ]
      },
      "BoxCreateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
          },
          "vcpus": {
            "type": "integer",
            "minimum": 1,
            "maximum": 64
          },
          "memoryMiB": {
            "type": "integer",
            "minimum": 256,
            "maximum": 262144
          },
          "diskGiB": {
            "type": "integer",
            "minimum": 1,
            "maximum": 2048
          },
          "start": {
            "default": true,
            "type": "boolean"
          },
          "snapshotId": {
            "type": "string",
            "format": "uuid"
          },
          "baseImage": {
            "type": "string",
            "enum": [
              "default",
              "omarchy"
            ]
          }
        }
      },
      "BoxScaleRequest": {
        "type": "object",
        "properties": {
          "memoryMiB": {
            "type": "integer",
            "minimum": 256,
            "maximum": 262144
          }
        },
        "required": [
          "memoryMiB"
        ],
        "additionalProperties": false
      },
      "ExecRequest": {
        "type": "object",
        "properties": {
          "argv": {
            "minItems": 1,
            "maxItems": 1024,
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 65536
            }
          },
          "timeoutMs": {
            "type": "integer",
            "minimum": 100,
            "maximum": 9007199254740991
          },
          "maxOutputBytes": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "cwd": {
            "type": "string",
            "maxLength": 4096,
            "format": "starts_with",
            "pattern": "^\\/.*"
          }
        },
        "required": [
          "argv"
        ]
      },
      "SnapshotCreateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9._-]{0,62}$"
          }
        },
        "required": [
          "name"
        ]
      },
      "SnapshotRestoreRequest": {
        "type": "object",
        "properties": {
          "snapshotId": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "snapshotId"
        ]
      },
      "ServiceAccountCreateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
          },
          "role": {
            "default": "member",
            "type": "string",
            "enum": [
              "admin",
              "member"
            ]
          }
        },
        "required": [
          "name"
        ]
      },
      "ApiKeyCreateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9._-]{0,62}$"
          },
          "scope": {
            "type": "string",
            "enum": [
              "read",
              "write"
            ]
          },
          "expiresInDays": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3650
          }
        },
        "required": [
          "name",
          "scope"
        ]
      },
      "MemberCreateRequest": {
        "type": "object",
        "properties": {
          "identity": {
            "type": "string",
            "minLength": 3,
            "maxLength": 254
          },
          "role": {
            "default": "member",
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member"
            ]
          }
        },
        "required": [
          "identity"
        ]
      },
      "SshKeyCreateRequest": {
        "type": "object",
        "properties": {
          "publicKey": {
            "type": "string",
            "maxLength": 1000
          }
        },
        "required": [
          "publicKey"
        ]
      },
      "DeviceTokenRequest": {
        "type": "object",
        "properties": {
          "deviceCode": {
            "type": "string",
            "minLength": 10,
            "maxLength": 200
          }
        },
        "required": [
          "deviceCode"
        ]
      },
      "Box": {
        "type": "object",
        "properties": {
          "storage": {
            "$ref": "#/components/schemas/StorageSummary",
            "description": "The state of background checkpoint uploads of the Box's disk and saved memory to remote storage. Every Box response includes it. It is informational: no route restores a Box from a checkpoint."
          },
          "id": {
            "type": "string",
            "description": "The Box's ID. It never changes."
          },
          "baseImage": {
            "$ref": "#/components/schemas/BaseImageId",
            "description": "The base image the Box's disk was created from. A Box created from a snapshot has its source Box's image."
          },
          "org": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The organization's ID."
              },
              "name": {
                "type": "string",
                "description": "The organization's name: the `ORG` in `ORG/NAME`."
              }
            },
            "required": [
              "id",
              "name"
            ],
            "description": "The organization that owns the Box."
          },
          "name": {
            "type": "string",
            "description": "Unique among the organization's Boxes that are not deleted: the `NAME` in `ORG/NAME`."
          },
          "node": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The Node's ID."
              },
              "name": {
                "type": "string",
                "description": "The Node's name."
              },
              "healthy": {
                "type": "boolean",
                "description": "Whether the Node sent a heartbeat in the last 20 s. While it is `false`, `state` is `unknown`."
              }
            },
            "required": [
              "id",
              "name",
              "healthy"
            ],
            "description": "The Node (host) the Box runs on. A Box never moves to another Node."
          },
          "spec": {
            "type": "object",
            "properties": {
              "vcpus": {
                "type": "number",
                "description": "Virtual CPUs."
              },
              "memoryMiB": {
                "type": "number",
                "description": "Memory in MiB."
              },
              "diskGiB": {
                "type": "number",
                "description": "Root disk size in GiB."
              }
            },
            "required": [
              "vcpus",
              "memoryMiB",
              "diskGiB"
            ],
            "description": "The CPU, memory and disk reserved for the Box on its Node for its whole life, also while it is stopped or hibernated. A free Box (`pooled`) gives its CPU and memory back while it is stopped or hibernated."
          },
          "desired": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "running",
                  "stopped",
                  "hibernated",
                  "deleted"
                ],
                "description": "Set by create, start, stop and hibernate, and by automatic hibernation; `deleted` once a delete is accepted. A failed hibernation sets it back to `running`."
              },
              "generation": {
                "type": "number",
                "description": "Advances with every accepted start, stop, hibernate and delete, with automatic hibernation and with a failed hibernation. A request has taken effect when `observed.generation` reaches it."
              }
            },
            "required": [
              "state",
              "generation"
            ],
            "description": "What the Box was last asked to be."
          },
          "observed": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "state": {
                    "type": "string",
                    "description": "The state the Node reported. Act on `state`, which also accounts for stale reports."
                  },
                  "generation": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "The desired generation the Node had applied when it reported."
                  },
                  "observedAt": {
                    "type": "string",
                    "description": "When the Node reported."
                  },
                  "ageSeconds": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Seconds since the report, rounded."
                  },
                  "fresh": {
                    "type": "boolean",
                    "description": "Whether the report is under 30 s old, comes from the Node's current service session and boot, and the Node is healthy."
                  },
                  "error": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Why the last start or action failed, or why the last hibernation failed; otherwise `null`."
                  }
                },
                "required": [
                  "state",
                  "generation",
                  "ageSeconds",
                  "fresh",
                  "error"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The Node's latest report on the Box, or `null` before its first report."
          },
          "state": {
            "$ref": "#/components/schemas/BoxState",
            "description": "The observed state when the report is fresh. `pending` before the Node's first report, `unknown` when the report is stale or the Node is unhealthy, which means neither running nor stopped, and `deleted` once the Node has removed a deleted Box."
          },
          "hibernateAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the Box hibernates automatically: 60 minutes after it first reported `running` since its latest start; see [Box lifecycle](https://docs.cubicle.sh/concepts/lifecycle/#boxes-hibernate-60-minutes-after-each-start). `null` until that report, and whenever the Box is not desired `running` or `restartRequired` is `true`. After a failed hibernation it stays in the past until the retry."
          },
          "hibernationRetryAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When Cubicle tries again to hibernate the Box after a failed attempt, 30 s after the first failure and doubling up to 5 min; otherwise `null`."
          },
          "pooled": {
            "type": "boolean",
            "description": "A free Box: its CPU and memory are reserved only while it runs."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a stopped or hibernated free Box is deleted unless it is started again."
          },
          "restartRequired": {
            "type": "boolean",
            "description": "Whether the Box's host restarted while the Box was desired `running`. The Box stays down until a start request, which clears it; a stop also clears it."
          },
          "connectionGeneration": {
            "type": "number",
            "description": "Advances with every start, stop, hibernation (automatic ones too), delete and restore, and when the Box's host restarts, but not when a failed hibernation returns the Box to running. SSH and private-network traffic reaches the Box only once it runs at the new value; see [Generations](https://docs.cubicle.sh/concepts/lifecycle/#generations-show-when-a-request-has-taken-effect)."
          },
          "privateIp": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Box's IPv4 address on its Node's private subnet, reachable only over Cubicle's private network. `null` until the Box is first provisioned; it then stays the same for the Box's life."
          },
          "console": {
            "type": "object",
            "properties": {
              "ready": {
                "type": "boolean",
                "description": "`true` when the console can be opened."
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Why not, when `ready` is `false`: `deleted`, `not_requested` (not desired `running`), `restart_required`, `node_unhealthy`, `console_observation_stale` or `console_unavailable`."
              }
            },
            "required": [
              "ready",
              "reason"
            ],
            "description": "Whether the Dashboard console can be used now."
          },
          "ssh": {
            "type": "object",
            "properties": {
              "ready": {
                "type": "boolean",
                "description": "`true` when the Box is running at its current generation on a healthy Node and has published its SSH host key."
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The first unmet condition, when `ready` is `false`: `deleted`, `not_requested` (not desired `running`), `restart_required`, `node_unhealthy`, `inventory_stale`, `observation_stale`, `not_running`, `generation_pending`, `endpoint_missing` or `host_key_pending`."
              }
            },
            "required": [
              "ready",
              "reason"
            ],
            "description": "Whether the Box is ready for commands and SSH now."
          },
          "deletion": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "complete",
              null
            ],
            "description": "`pending` from an accepted delete until the Node has removed the Box, after which listings no longer show it; otherwise `null`. `complete` appears only when an `Idempotency-Key` replay of a create returns a Box removed since."
          },
          "createdAt": {
            "type": "string",
            "description": "When the Box was created."
          }
        },
        "required": [
          "id",
          "baseImage",
          "org",
          "name",
          "node",
          "spec",
          "desired",
          "observed",
          "state",
          "hibernateAt",
          "hibernationRetryAt",
          "pooled",
          "expiresAt",
          "restartRequired",
          "connectionGeneration",
          "privateIp",
          "console",
          "ssh",
          "deletion",
          "createdAt"
        ]
      },
      "StorageSummary": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Whether checkpoint uploads are turned on."
          },
          "status": {
            "type": "string",
            "enum": [
              "base",
              "unprotected",
              "uploading",
              "failed",
              "protected",
              "older"
            ],
            "description": "`base`: no checkpoint yet, and the Box is desired `running` or has not run since uploads were turned on. `unprotected`: no checkpoint yet in any other case. `uploading` and `failed`: a checkpoint upload is in progress or failed and will be retried. `protected`: the latest checkpoint matches the Box. `older`: the Box changed after the latest checkpoint."
          },
          "checkpointId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The latest uploaded checkpoint, or `null`."
          },
          "checkpointAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the latest checkpoint was published, or `null`."
          },
          "current": {
            "type": "boolean",
            "description": "Whether the latest checkpoint matches the Box."
          },
          "processedBytes": {
            "type": "number",
            "description": "Bytes the current upload has processed."
          },
          "totalBytes": {
            "type": "number",
            "description": "Bytes the current upload has to process."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the current upload failed, or `null`."
          },
          "retryAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a failed upload is retried, or `null`."
          }
        },
        "required": [
          "enabled",
          "status",
          "checkpointId",
          "checkpointAt",
          "current",
          "processedBytes",
          "totalBytes",
          "error",
          "retryAt"
        ]
      },
      "BaseImageId": {
        "type": "string",
        "enum": [
          "default",
          "omarchy"
        ]
      },
      "BoxState": {
        "type": "string",
        "enum": [
          "pending",
          "unknown",
          "deleted",
          "absent",
          "provisioning",
          "starting",
          "running",
          "stopping",
          "stopped",
          "hibernating",
          "hibernated",
          "deleting",
          "failed"
        ],
        "description": "Observed runtime states plus the Controller's derived `pending`, `unknown` and `deleted`."
      },
      "CreationBase": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/BaseImageId",
            "description": "The base image's ID, for `baseImage` when creating a Box."
          },
          "name": {
            "type": "string",
            "description": "The base image's display name."
          },
          "spec": {
            "$ref": "#/components/schemas/Resources",
            "description": "The size a Box created from this image gets when the request omits it."
          },
          "available": {
            "type": "boolean",
            "description": "Whether a healthy Node offers the image now. Capacity is not checked, so creating a Box can still fail with `503 capacity`."
          }
        },
        "required": [
          "id",
          "name",
          "spec",
          "available"
        ]
      },
      "Resources": {
        "type": "object",
        "properties": {
          "vcpus": {
            "type": "number",
            "description": "Virtual CPUs."
          },
          "memoryMiB": {
            "type": "number",
            "description": "Memory in MiB."
          },
          "diskGiB": {
            "type": "number",
            "description": "Root disk size in GiB."
          }
        },
        "required": [
          "vcpus",
          "memoryMiB",
          "diskGiB"
        ]
      },
      "CreationSnapshot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The snapshot's ID, for `snapshotId` when creating a Box."
          },
          "name": {
            "type": "string",
            "description": "The name it was taken with. Names need not be unique."
          },
          "createdAt": {
            "type": "string",
            "description": "When it was requested."
          },
          "box": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The source Box's ID."
              },
              "name": {
                "type": "string",
                "description": "The source Box's name."
              }
            },
            "required": [
              "id",
              "name"
            ],
            "description": "The Box it was taken from."
          },
          "node": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The Node's ID."
              },
              "name": {
                "type": "string",
                "description": "The Node's name."
              }
            },
            "required": [
              "id",
              "name"
            ],
            "description": "The Node that holds it. A Box created from it is placed on this Node."
          },
          "spec": {
            "$ref": "#/components/schemas/Resources",
            "description": "The source Box's current size, which a Box created from it gets by default. Its disk can be made larger, not smaller."
          }
        },
        "required": [
          "id",
          "name",
          "createdAt",
          "box",
          "node",
          "spec"
        ],
        "description": "Completed snapshots the requester may use to create a Box in this organization."
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The key's ID, used to revoke it."
          },
          "name": {
            "type": "string",
            "description": "The key's name, unique among its service account's keys that are not revoked."
          },
          "scope": {
            "$ref": "#/components/schemas/ApiKeyScope",
            "description": "What the key may call: `read` allows only `GET` routes, `write` every route that accepts API keys."
          },
          "hint": {
            "type": "string",
            "description": "The token's first characters, to recognize it without the secret."
          },
          "createdBy": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The creator's user ID."
              },
              "identity": {
                "type": "string",
                "description": "The creator's sign-in identity."
              }
            },
            "required": [
              "id",
              "identity"
            ],
            "description": "The person who created the key; the key keeps working after they leave the organization."
          },
          "createdAt": {
            "type": "string",
            "description": "When the key was created (ISO 8601, UTC)."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the key stops working (ISO 8601, UTC), or `null` if it never expires."
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the key last authenticated a request (ISO 8601, UTC), or `null` if it never has."
          }
        },
        "required": [
          "id",
          "name",
          "scope",
          "hint",
          "createdBy",
          "createdAt",
          "expiresAt",
          "lastUsedAt"
        ],
        "description": "A service account's API key. `token` is returned once, by the create call only."
      },
      "ApiKeyScope": {
        "type": "string",
        "enum": [
          "read",
          "write"
        ],
        "description": "`read` keys may only use GET routes; `write` keys may use every route that accepts keys."
      },
      "CreatedApiKey": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "The secret API key (`cbk_…`), shown only in this response; send it as `Authorization: Bearer cbk_…`."
          },
          "id": {
            "type": "string",
            "description": "The key's ID, used to revoke it."
          },
          "name": {
            "type": "string",
            "description": "The key's name, unique among its service account's keys that are not revoked."
          },
          "scope": {
            "$ref": "#/components/schemas/ApiKeyScope",
            "description": "What the key may call: `read` allows only `GET` routes, `write` every route that accepts API keys."
          },
          "hint": {
            "type": "string",
            "description": "The token's first characters, to recognize it without the secret."
          },
          "createdBy": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The creator's user ID."
              },
              "identity": {
                "type": "string",
                "description": "The creator's sign-in identity."
              }
            },
            "required": [
              "id",
              "identity"
            ],
            "description": "The person who created the key; the key keeps working after they leave the organization."
          },
          "createdAt": {
            "type": "string",
            "description": "When the key was created (ISO 8601, UTC)."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the key stops working (ISO 8601, UTC), or `null` if it never expires."
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the key last authenticated a request (ISO 8601, UTC), or `null` if it never has."
          }
        },
        "required": [
          "createdAt",
          "createdBy",
          "expiresAt",
          "hint",
          "id",
          "lastUsedAt",
          "name",
          "scope",
          "token"
        ],
        "description": "A new API key with its secret `token`, which only the create call returns."
      },
      "ExecResult": {
        "type": "object",
        "properties": {
          "exitCode": {
            "type": "number",
            "description": "The command's exit status. `124` when it timed out, and `127` when the command was not found."
          },
          "stdout": {
            "type": "string",
            "description": "Standard output, decoded as UTF-8 with invalid bytes replaced."
          },
          "stderr": {
            "type": "string",
            "description": "Standard error, decoded as UTF-8 with invalid bytes replaced."
          },
          "timedOut": {
            "type": "boolean",
            "description": "Whether the command ran past `timeoutMs` and was killed; `exitCode` is then `124`."
          },
          "truncated": {
            "type": "boolean",
            "description": "Whether stdout and stderr together went past `maxOutputBytes`; the rest was discarded."
          }
        },
        "required": [
          "exitCode",
          "stdout",
          "stderr",
          "timedOut",
          "truncated"
        ]
      },
      "Grant": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "description": "The member's user ID: a person's or a service account's."
          },
          "identity": {
            "type": "string",
            "description": "The member's identity: an email address for a person, `ORG/NAME` for a service account."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "revoked"
            ],
            "description": "`active`, or `revoked` for a grant that was revoked. Revoked grants stay listed."
          },
          "guestAccount": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member's Unix account in the Box: `jv_` followed by the first 12 hex characters of their user ID."
          },
          "desiredRevision": {
            "type": [
              "number",
              "null"
            ],
            "description": "The revision of the member's account that the Box should apply. It advances with every access change for the member, such as a new SSH key or a revoked grant."
          },
          "appliedRevision": {
            "type": [
              "number",
              "null"
            ],
            "description": "The revision the Box has applied. The latest access change is in effect when it equals `desiredRevision`."
          },
          "accountStatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "applied",
              "failed",
              null
            ],
            "description": "`pending`, `applied` or `failed`: whether the Box has applied the member's account. Only `GET /v1/boxes` returns it."
          }
        },
        "required": [
          "userId",
          "identity",
          "status",
          "guestAccount",
          "desiredRevision",
          "appliedRevision",
          "accountStatus"
        ]
      },
      "Me": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The signed-in user's ID, or the ID of the service account that owns the API key."
          },
          "identity": {
            "type": "string",
            "description": "The user's sign-in identity, usually an email address, or `ORG/NAME` for a service account."
          },
          "operator": {
            "type": "boolean",
            "description": "Whether the caller is a Cubicle platform operator; always `false` for an API key."
          },
          "orgs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "role": {
                  "$ref": "#/components/schemas/Role"
                },
                "personal": {
                  "type": "boolean",
                  "description": "The caller's own personal organization."
                },
                "boxCreateAllowed": {
                  "type": "boolean",
                  "description": "Whether members may create Boxes; personal organizations need an operator's approval."
                }
              },
              "required": [
                "id",
                "name",
                "role",
                "personal",
                "boxCreateAllowed"
              ]
            },
            "description": "The organizations where the caller is an active member, ordered by name, with the caller's `role` in each; with an API key, only the key's organization."
          },
          "apiKey": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The API key's ID."
              },
              "orgId": {
                "type": "string",
                "description": "The ID of the organization the key belongs to; the key reaches nothing outside it."
              },
              "scope": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/ApiKeyScope"
                  },
                  {
                    "type": "string",
                    "const": "application"
                  }
                ],
                "description": "The key's scope; a `read` key may call only `GET` routes."
              },
              "connection": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "applicationId": {
                    "type": "string"
                  },
                  "permissions": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/ApplicationPermission"
                    }
                  }
                },
                "required": [
                  "id",
                  "applicationId",
                  "permissions"
                ],
                "description": "For an application connection's key: the connection and what it may do."
              }
            },
            "required": [
              "id",
              "orgId",
              "scope"
            ],
            "description": "Present when a service account's API key authenticated; `orgs` then holds only its org."
          }
        },
        "required": [
          "id",
          "identity",
          "orgs"
        ]
      },
      "Role": {
        "type": "string",
        "enum": [
          "owner",
          "admin",
          "member"
        ]
      },
      "ApplicationPermission": {
        "type": "string",
        "enum": [
          "boxes:read",
          "boxes:create",
          "boxes:power",
          "boxes:resize",
          "boxes:exec",
          "boxes:snapshot",
          "boxes:delete",
          "ssh",
          "connect"
        ]
      },
      "Operation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The operation's ID."
          },
          "boxId": {
            "type": "string",
            "description": "The ID of the Box it runs on."
          },
          "type": {
            "type": "string",
            "enum": [
              "exec",
              "snapshot",
              "restore"
            ],
            "description": "`exec` for a command, `snapshot` or `restore`."
          },
          "state": {
            "$ref": "#/components/schemas/OperationState",
            "description": "`pending` until the Box's Node picks it up, then `running`, and finally `done`, `failed` or `unknown`. `unknown` means it was interrupted and its outcome is not known; it is never retried."
          },
          "attempt": {
            "type": "number",
            "description": "How many times a Node has picked it up: `0` while `pending`, then `1`."
          },
          "progress": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reserved for progress notes. Current Nodes do not send any, so it is `null`."
          },
          "result": {
            "description": "`null` until `done`. A done `exec` holds an `ExecResult`, shown only to the member who requested it and `null` for everyone else. A done `restore` holds `{ \"snapshotId\": … }`. A done `snapshot` holds an internal manifest; read the snapshot from the Box's snapshot list instead."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it `failed` or became `unknown`; otherwise `null`."
          },
          "createdAt": {
            "type": "string",
            "description": "When it was requested."
          },
          "startedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a Node picked it up, or `null` while `pending`."
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When it reached `done`, `failed` or `unknown`, or `null` before."
          }
        },
        "required": [
          "id",
          "boxId",
          "type",
          "state",
          "attempt",
          "progress",
          "result",
          "error",
          "createdAt",
          "startedAt",
          "completedAt"
        ]
      },
      "OperationState": {
        "type": "string",
        "enum": [
          "pending",
          "running",
          "done",
          "failed",
          "unknown"
        ]
      },
      "ServiceAccount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The service account's user ID: `{account}` in service account routes and `{user}` in grant and member routes."
          },
          "name": {
            "type": "string",
            "description": "The service account's name, the part of `identity` after the slash."
          },
          "identity": {
            "type": "string",
            "description": "The service account's identity, `ORG/NAME`, as member and grant lists show it."
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "member"
            ],
            "description": "The service account's role in its organization: `member` or `admin`, never `owner`."
          },
          "createdAt": {
            "type": "string",
            "description": "When the service account was created (ISO 8601, UTC)."
          },
          "keys": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKey"
            },
            "description": "The service account's API keys that are not revoked, oldest first and without their tokens; expired keys stay listed until revoked."
          }
        },
        "required": [
          "id",
          "name",
          "identity",
          "role",
          "createdAt",
          "keys"
        ],
        "description": "A non-interactive member that one organization owns, identified as ORG/NAME. It receives Box grants like a person, holds the organization's API keys and never signs in."
      },
      "SessionInfo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The session's ID, used to revoke it."
          },
          "client": {
            "$ref": "#/components/schemas/SessionClient",
            "description": "Where the session was signed in: `dashboard` for the Dashboard, `cli` for `cubicle login` and other device sign-ins."
          },
          "createdAt": {
            "type": "string",
            "description": "When the session was signed in (ISO 8601, UTC)."
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the session last authenticated a request (ISO 8601, UTC), or `null` if it has not been used since sign-in."
          },
          "expiresAt": {
            "type": "string",
            "description": "When the session ends unless it is revoked first (ISO 8601, UTC); use does not extend it."
          },
          "current": {
            "type": "boolean",
            "description": "The session that made this request."
          }
        },
        "required": [
          "id",
          "client",
          "createdAt",
          "lastUsedAt",
          "expiresAt",
          "current"
        ],
        "description": "One of the caller's live API sessions, without its token."
      },
      "SessionClient": {
        "type": "string",
        "enum": [
          "dashboard",
          "cli"
        ],
        "description": "Where an API session was signed in: the Dashboard (password, Google) or `cubicle login`."
      },
      "Snapshot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The snapshot's ID, for restoring it or creating Boxes from it."
          },
          "name": {
            "type": "string",
            "description": "The name it was taken with. Names need not be unique."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "complete",
              "failed"
            ],
            "description": "`pending` while its operation is pending or running, then `complete` or `failed`."
          },
          "operationId": {
            "type": "string",
            "description": "The snapshot operation that takes it."
          },
          "createdAt": {
            "type": "string",
            "description": "When it was requested."
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When it became `complete` or `failed`, or `null` before."
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "operationId",
          "createdAt",
          "completedAt"
        ]
      },
      "SshInfo": {
        "type": "object",
        "properties": {
          "boxId": {
            "type": "string",
            "description": "The Box's ID."
          },
          "granted": {
            "type": "boolean",
            "description": "Whether the caller holds an active grant on the Box."
          },
          "guestAccount": {
            "type": [
              "string",
              "null"
            ],
            "description": "The caller's Unix account in the Box, which is the SSH user name; `null` if the caller was never granted the Box."
          },
          "hostKeyAlias": {
            "type": "string",
            "description": "The name to pin the host key under, as SSH's `HostKeyAlias`: `jevbox-` followed by the Box ID."
          },
          "hostKey": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Box's SSH host public key, only when it was published for the current `connectionGeneration`; otherwise `null`."
          },
          "connectionGeneration": {
            "type": "number",
            "description": "The Box's current connection generation. The host key and the caller's account are valid only for it."
          },
          "ready": {
            "type": "boolean",
            "description": "Whether the caller can SSH into the Box now."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "The first unmet condition, when `ready` is `false`: one of the Box's `ssh.reason` values, or `user_disabled`, `membership_inactive`, `grant_inactive`, `no_active_key` (no SSH key registered) or `authorization_pending` (the Box has not applied the caller's latest account change)."
          }
        },
        "required": [
          "boxId",
          "granted",
          "guestAccount",
          "hostKeyAlias",
          "hostKey",
          "connectionGeneration",
          "ready",
          "reason"
        ]
      },
      "SshKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The key's ID, used to remove it."
          },
          "fingerprint": {
            "type": "string",
            "description": "The key's SHA-256 fingerprint, as `ssh-keygen -l` prints it (`SHA256:…`)."
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "The text that followed the key when it was added, cut to 200 characters; empty if there was none."
          },
          "publicKey": {
            "type": "string",
            "description": "The public key as stored: `ssh-ed25519` and the base64 key, without the comment."
          },
          "createdAt": {
            "type": "string",
            "description": "When the key was added (ISO 8601, UTC)."
          }
        },
        "required": [
          "id",
          "fingerprint",
          "comment",
          "createdAt"
        ],
        "description": "One of the caller's SSH public keys."
      },
      "ApiSession": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "The session's secret bearer token (`jvs_…`), returned only once; send it as `Authorization: Bearer jvs_…`."
          },
          "identity": {
            "type": "string",
            "description": "The sign-in identity of the person the session belongs to."
          },
          "expiresAt": {
            "type": "string",
            "description": "When the session ends unless it is revoked first (ISO 8601, UTC)."
          }
        },
        "required": [
          "token",
          "identity",
          "expiresAt"
        ],
        "description": "A new API session from any sign-in method."
      }
    },
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A service account API key (`cbk_…`), used directly as the bearer token. `read` keys may call only `GET` routes."
      },
      "session": {
        "type": "http",
        "scheme": "bearer",
        "description": "A sign-in session token (`jvs_…`) from the Dashboard, `cubicle login` or the device authorization flow."
      }
    }
  }
}
