{"components":{"headers":{"Deprecation":{"description":"RFC 9745: the route or a field of the request is deprecated (since the date given, `@\u003cunix seconds\u003e`). Routes also send `Link: \u003csuccessor\u003e; rel=\"successor-version\"`.","schema":{"examples":["@1791331200"],"type":"string"}}},"parameters":{"FilesPath":{"description":"Absolute path, at most 4096 bytes (default \"/\").","in":"query","name":"path","schema":{"default":"/","type":"string"}},"IdempotencyKey":{"description":"Client-generated key, 1-200 printable ASCII characters, scoped to the\nuser. Same key and same method+path+body returns the original response;\nsame key with a different request returns `409 idempotency_conflict`.\nRecords are kept for 7 days.\n","in":"header","name":"Idempotency-Key","required":true,"schema":{"maxLength":200,"minLength":1,"pattern":"^[\\x21-\\x7e]+$","type":"string"}},"MachineID":{"description":"Sandbox ID (`m_` + 26 lowercase base32 characters).","in":"path","name":"id","required":true,"schema":{"pattern":"^m_[a-z2-7]{26}$","type":"string"}},"MachineRef":{"description":"Sandbox ID (`m_` + 26 lowercase base32 characters) or the sandbox's name (unique among your sandboxes; names never contain `_`, so the two never collide). Older servers take only the ID and answer `404` for a name.","in":"path","name":"id","required":true,"schema":{"pattern":"^(m_[a-z2-7]{26}|[a-z0-9][a-z0-9-]{0,62})$","type":"string"}},"SnapshotID":{"description":"Snapshot ID (`sn_`, or `im_` for older snapshots, + 26 lowercase base32 characters).","in":"path","name":"id","required":true,"schema":{"type":"string"}}},"responses":{"BadRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`bad_request`: invalid input or missing Idempotency-Key. `spec_not_allowed`: sandbox sizes are fixed (4 vCPUs, 8 GB memory by default; `limits.machineSpec`) and the request names another size (create, create from a snapshot, fork), or someone other than an admin tries to resize a sandbox. The message gives the size the sandbox gets; leaving the size fields out always works. `vault_unavailable` (503): a Vault item was sealed under a master key this server does not have (docs/vault.md)."},"Conflict":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`operation_conflict` (another operation is active; `operationId` names it,\nor the transition is not allowed), `idempotency_conflict` (key reused for\na different request), `machine_not_running` (the sandbox is not running), `desktop_not_running` (the\nVNC endpoint before the desktop started), or for Orca connections\n`orca_unsupported` and `runtime_not_ready` (retryable).\n"},"Internal":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`internal`: unexpected server error."},"MetricsUnavailable":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`metrics_unavailable`: the metrics store is turned off (`metrics.storeUrl` \"off\"), not running or failing. Retryable when it is down."},"NotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`not_found`: no such resource (also for deleted sandboxes)."},"OperationAccepted":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}},"description":"The mutation was accepted (or replayed for the same idempotency key). Poll the operation."},"PermissionDenied":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`permission_denied`: the user is not allowed (not on the allowlist, or not the owner), or an API key was used where only a sign-in session is; `insufficient_scope`: the API key lacks the route's scope."},"QuotaExceeded":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`quota_exceeded`: one of your limits was reached: the number of sandboxes (5, in any state, stopped and paused included), stored data (200 GB), snapshots, API keys, execs or terminals. The message says which, e.g. \"sandbox limit reached (5 of 5); delete a sandbox first\". Delete a sandbox or snapshot, or ask an admin to raise your limit."},"Unauthenticated":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`unauthenticated`: missing, expired or revoked token, or an unknown API key; `api_key_expired` or `api_key_revoked` for such an API key."},"Unavailable":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`no_capacity` (there is no room for the sandbox right now; the message says what is short) or `host_unavailable` (the sandbox cannot be reached right now). Usually `retryable`; try again shortly."}},"schemas":{"APIKey":{"properties":{"createdAt":{"format":"date-time","type":"string"},"expiresAt":{"format":"date-time","type":["string","null"]},"id":{"pattern":"^key_[a-z2-7]{26}$","type":"string"},"lastUsedAt":{"description":"Updated at most once a minute.","format":"date-time","type":["string","null"]},"name":{"type":"string"},"prefix":{"description":"First 8 characters after `ibk_`, for recognizing the key.","type":"string"},"scopes":{"items":{"$ref":"#/components/schemas/APIKeyScope"},"type":"array"}},"required":["id","name","prefix","scopes","createdAt","lastUsedAt","expiresAt"],"type":"object"},"APIKeyList":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/APIKey"},"type":"array"}},"required":["keys"],"type":"object"},"APIKeyScope":{"enum":["machines:read","machines:write","machines:delete","images:read","images:write","images:delete"],"type":"string"},"Activation":{"properties":{"activated":{"type":"boolean"},"activatedAt":{"description":"When the account was activated.","format":"date-time","type":["string","null"]},"admin":{"description":"The account is an administrator now (an admin code grants the role).","type":"boolean"}},"required":["activated","activatedAt","admin"],"type":"object"},"ActivityLease":{"properties":{"expiresAt":{"format":"date-time","type":"string"},"id":{"type":"string"},"machineId":{"type":"string"}},"required":["id","machineId","expiresAt"],"type":"object"},"AdminConnection":{"properties":{"bytesIn":{"description":"Client to sandbox.","format":"int64","type":"integer"},"bytesOut":{"description":"Sandbox to client.","format":"int64","type":"integer"},"grantId":{"description":"Connection grant (`cn_…`) of the certificate.","type":"string"},"id":{"description":"Gateway connection ID (`gc_\u003cn\u003e`).","type":"string"},"machineId":{"type":"string"},"machineName":{"type":"string"},"ownerId":{"format":"int64","type":"integer"},"ownerLogin":{"type":"string"},"remoteAddr":{"type":"string"},"since":{"format":"date-time","type":"string"}},"required":["id","grantId","machineId","machineName","ownerId","ownerLogin","remoteAddr","since","bytesIn","bytesOut"],"type":"object"},"AdminConnectionList":{"properties":{"connections":{"items":{"$ref":"#/components/schemas/AdminConnection"},"type":"array"}},"required":["connections"],"type":"object"},"AdminHost":{"properties":{"address":{"type":"string"},"allocatableMemMiB":{"description":"(memMiB - hostReserve.memMiB) * memOvercommit, capped by maxMemMiB; null before the first report.","type":["integer","null"]},"allocatableVCPUs":{"description":"What the scheduler may reserve: (vcpus - hostReserve.vcpus) * cpuOvercommit, capped by maxVCPUs; null before the first report.","type":["integer","null"]},"configDisabled":{"description":"`disabled` in server.json (no node client).","type":"boolean"},"configured":{"description":"Listed in server.json.","type":"boolean"},"cpuClass":{"description":"CPU class from the host report, else the one the node enrolled with; absent when unknown.","type":"string"},"enabled":{"description":"Admin scheduling toggle.","type":"boolean"},"enrolled":{"description":"Registered by its node (node enrollment) and not listed in server.json.","type":"boolean"},"machines":{"description":"Live sandboxes whose local copy is on the host","running or not.":null,"type":"integer"},"maxMemMiB":{"description":"Configured absolute cap (0: none).","type":"integer"},"maxVCPUs":{"description":"Configured absolute cap (0: none).","type":"integer"},"memMiB":{"description":"Their memory.","type":"integer"},"name":{"type":"string"},"pool":{"description":"The host's pool (docs/hybrid-pools.md), e.g. \"default\"; absent on a site without pools.","type":"string"},"poolMismatch":{"description":"Why the host is not scheduled on: its node reports another pool or CPU class than the server's configuration gives it. Absent when they match.","type":"string"},"report":{"oneOf":[{"$ref":"#/components/schemas/HostReport"},{"type":"null"}]},"reportedAt":{"format":"date-time","type":["string","null"]},"reservedMemMiB":{"description":"Their memory plus memOverheadMiB each.","type":"integer"},"reservedVCPUs":{"description":"vCPUs of the sandboxes that hold resources on the host (running, or being created, started or resumed there).","type":"integer"},"stale":{"description":"No report","or one too old to schedule on.":null,"type":"boolean"},"vcpus":{"description":"Their vCPUs.","type":"integer"}},"required":["name","address","configured","configDisabled","enrolled","enabled","report","reportedAt","stale","machines","vcpus","memMiB","maxVCPUs","maxMemMiB","allocatableVCPUs","allocatableMemMiB","reservedVCPUs","reservedMemMiB"],"type":"object"},"AdminHostList":{"properties":{"hosts":{"items":{"$ref":"#/components/schemas/AdminHost"},"type":"array"}},"required":["hosts"],"type":"object"},"AdminMachine":{"properties":{"host":{"type":["string","null"]},"idleSuspendAt":{"description":"Running sandboxes with idle auto-suspend: the earliest time it can suspend them (the idle period after the last request activity, activity lease or state change; the guest must also report idle, so it may come later).","format":"date-time","type":"string"},"lastAccessAt":{"description":"The last time a user or agent used the sandbox: an SSH gateway session, exec, file read, dashboard terminal or desktop, a public port request, or an API call that acts on it (not reads). Recorded at most once a minute per sandbox; null when none was recorded.","format":"date-time","type":["string","null"]},"machine":{"$ref":"#/components/schemas/Machine"},"operation":{"description":"The active operation.","oneOf":[{"$ref":"#/components/schemas/Operation"},{"type":"null"}]},"ownerId":{"format":"int64","type":"integer"},"ownerLogin":{"type":"string"},"ownerName":{"type":"string"},"pool":{"description":"The sandbox's host pool (docs/hybrid-pools.md); absent on a site without pools.","type":"string"},"publicPorts":{"description":"Published ports.","type":"integer"},"snapshots":{"description":"Live snapshots taken from the sandbox.","type":"integer"},"stateChangedAt":{"description":"When the state last changed.","format":"date-time","type":"string"},"usage":{"$ref":"#/components/schemas/AdminMachineUsage"}},"required":["machine","ownerId","ownerLogin","host","operation","lastAccessAt","snapshots","publicPorts"],"type":"object"},"AdminMachineDetail":{"allOf":[{"$ref":"#/components/schemas/AdminMachine"},{"properties":{"operations":{"description":"The 20 most recent operations, newest first.","items":{"$ref":"#/components/schemas/Operation"},"type":"array"},"ports":{"items":{"$ref":"#/components/schemas/PublicPort"},"type":"array"}},"required":["operations","ports"],"type":"object"}]},"AdminMachineList":{"properties":{"hosts":{"description":"Every host.","items":{"type":"string"},"type":"array"},"limit":{"type":"integer"},"machines":{"items":{"$ref":"#/components/schemas/AdminMachine"},"type":"array"},"offset":{"type":"integer"},"templates":{"description":"The templates of live sandboxes.","items":{"type":"string"},"type":"array"},"total":{"description":"Sandboxes that match the filters.","type":"integer"}},"required":["machines","total","offset","hosts","templates"],"type":"object"},"AdminMachineUsage":{"description":"A running sandbox's load in its host's latest report.","properties":{"balloon":{"description":"The VM has a memory balloon.","type":"boolean"},"cpuCores":{"description":"CPU time per second (1.0 is one busy core).","type":"number"},"memoryBytes":{"description":"The VM cgroup's memory.current.","format":"int64","type":"integer"},"rssBytes":{"description":"The VMM process's resident memory.","format":"int64","type":"integer"},"sampledAt":{"format":"date-time","type":"string"}},"required":["sampledAt"],"type":"object"},"AdminOperation":{"properties":{"leaseOwner":{"type":["string","null"]},"leaseUntil":{"format":"date-time","type":["string","null"]},"machineName":{"type":"string"},"operation":{"$ref":"#/components/schemas/Operation"},"ownerId":{"format":"int64","type":"integer"},"ownerLogin":{"type":"string"},"stuck":{"description":"Queued or running for longer than stuckMinutes.","type":"boolean"}},"required":["operation","ownerId","ownerLogin","machineName","leaseOwner","leaseUntil","stuck"],"type":"object"},"AdminOperationList":{"properties":{"operations":{"items":{"$ref":"#/components/schemas/AdminOperation"},"type":"array"},"stuckMinutes":{"type":"integer"}},"required":["operations","stuckMinutes"],"type":"object"},"AdminStorage":{"properties":{"bucket":{"type":"string"},"client":{"description":"S3 client counters since server start.","oneOf":[{"$ref":"#/components/schemas/ObjectClientStats"},{"type":"null"}]},"configured":{"type":"boolean"},"endpoint":{"type":"string"},"gc":{"$ref":"#/components/schemas/GCSettings"},"jobs":{"items":{"$ref":"#/components/schemas/JobRun"},"type":"array"},"pools":{"description":"The object stores of the site's other host pools (docs/hybrid-pools.md); the fields above are the default pool's. Absent on a site without pools.","items":{"$ref":"#/components/schemas/StoragePool"},"type":"array"},"prefix":{"type":"string"},"usage":{"$ref":"#/components/schemas/UsageTotals"}},"required":["configured","gc","jobs","usage","client"],"type":"object"},"AdminUser":{"properties":{"accessGrantedAt":{"description":"When access was granted (the first sign-in).","format":"date-time","type":["string","null"]},"activatedAt":{"description":"When the account passed the invite gate (null: it must still redeem a code while the gate is on).","format":"date-time","type":["string","null"]},"admin":{"type":"boolean"},"allowed":{"description":"Effective access: the account exists and is not disabled.","type":"boolean"},"allowlisted":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"defaultLimits":{"$ref":"#/components/schemas/QuotaLimits","description":"The server.json limits."},"disabledAt":{"description":"Set while an admin disabled the account.","format":"date-time","type":["string","null"]},"email":{"description":"The provider-verified email from the user's sign-ins (Google's, or a verified GitHub email); absent: none known.","type":"string"},"id":{"format":"int64","type":"integer"},"lastLoginAt":{"format":"date-time","type":"string"},"limits":{"$ref":"#/components/schemas/QuotaLimits","description":"The user's effective limits (overrides","else defaults).":null},"login":{"type":"string"},"machines":{"type":"integer"},"maxMachines":{"description":"Sandbox limit override (null: the server.json limit, default 5).","type":["integer","null"]},"maxSnapshots":{"description":"Snapshot limit override (null: the server.json limit).","type":["integer","null"]},"maxStoredGiB":{"description":"Stored GiB limit override (null: the server.json limit).","type":["integer","null"]},"name":{"type":"string"},"pool":{"description":"Host pool assignment of the user's new sandboxes (docs/hybrid-pools.md); absent: none.","type":"string"},"snapshots":{"type":"integer"},"storedBytes":{"format":"int64","type":["integer","null"]}},"required":["id","login","createdAt","lastLoginAt","machines","snapshots","storedBytes","allowlisted","admin"],"type":"object"},"AdminUserList":{"properties":{"admins":{"items":{"format":"int64","type":"integer"},"type":"array"},"allowed":{"description":"Number of matching users with access before pagination.","type":"integer"},"allowlist":{"items":{"format":"int64","type":"integer"},"type":"array"},"limit":{"description":"Requested page size; absent when unbounded.","type":"integer"},"offset":{"description":"Number of matching users skipped.","type":"integer"},"total":{"description":"Number of matching users before pagination.","type":"integer"},"users":{"items":{"$ref":"#/components/schemas/AdminUser"},"type":"array"},"waitlisted":{"description":"Total users awaiting an invite","regardless of search or pagination.":null,"type":"integer"}},"required":["users","total","allowed","waitlisted","offset","allowlist","admins"],"type":"object"},"AutoSuspendAfterSeconds":{"description":"120-86400: pause the sandbox after this many idle seconds; 0: never, it stays on until paused or deleted; absent: 1800 (30 minutes), the default of every new sandbox.","type":"integer"},"CLITokenRequest":{"properties":{"code":{"description":"One-time code (`ibc_...`) from the redirect URI.","type":"string"},"codeVerifier":{"maxLength":128,"minLength":43,"type":"string"},"redirectUri":{"description":"The exact `redirect_uri` sent to `/auth/cli/start`.","type":"string"}},"required":["code","codeVerifier","redirectUri"],"type":"object"},"Connection":{"properties":{"certificate":{"description":"User certificate in authorized_keys format.","type":"string"},"expiresAt":{"format":"date-time","type":"string"},"host":{"type":"string"},"hostTrust":{"$ref":"#/components/schemas/HostTrust"},"id":{"also the certificate key ID.":null,"description":"Connection ID (`cn_…`)","type":"string"},"machineId":{"type":"string"},"port":{"type":"integer"},"transport":{"enum":["ssh"],"type":"string"},"username":{"type":"string"}},"required":["id","machineId","transport","host","port","username","certificate","expiresAt","hostTrust"],"type":"object"},"ConnectionInfo":{"properties":{"capabilities":{"description":"`mosh`: mosh works (a session that runs `mosh-server new` gets a relay UDP port in its `MOSH CONNECT` line).","items":{"enum":["pty","exec","port-forwarding","mosh"],"type":"string"},"type":"array"},"host":{"type":"string"},"hostTrust":{"$ref":"#/components/schemas/HostTrust"},"machineId":{"type":"string"},"port":{"type":"integer"},"transport":{"enum":["ssh"],"type":"string"},"username":{"description":"The sandbox ID.","type":"string"}},"required":["machineId","transport","host","port","username","hostTrust","capabilities"],"type":"object"},"CreateAPIKeyRequest":{"properties":{"expiresInDays":{"default":0,"description":"Days until the key expires, 1-365. 0 or absent: it does not expire.","maximum":365,"minimum":0,"type":"integer"},"name":{"maxLength":64,"minLength":1,"type":"string"},"scopes":{"items":{"$ref":"#/components/schemas/APIKeyScope"},"minItems":1,"type":"array"}},"required":["name","scopes"],"type":"object"},"CreateConnectionRequest":{"properties":{"publicKey":{"description":"Client public key in authorized_keys format.","type":"string"},"ttlSeconds":{"description":"Default 3600, max 86400.","maximum":86400,"minimum":1,"type":"integer"}},"required":["publicKey"],"type":"object"},"CreateInvitesRequest":{"properties":{"count":{"default":1,"description":"How many codes to make.","maximum":100,"minimum":1,"type":"integer"},"expiresAt":{"description":"Absent: the codes never expire. At most two years out.","format":"date-time","type":"string"},"maxUses":{"default":1,"description":"Accounts each code activates.","maximum":1000,"minimum":1,"type":"integer"},"note":{"maxLength":200,"type":"string"},"role":{"$ref":"#/components/schemas/InviteRole","default":"user"}},"type":"object"},"CreateMachineRequest":{"description":"Exactly one of `templateId` and `snapshotId`. Leave the size fields out.","properties":{"archiveSeconds":{"description":"Sets `archiveAt` to now plus this many seconds, independent of the timeout: the sandbox is stopped then if it is running. 0 or absent: none.","maximum":86400,"minimum":60,"type":"integer"},"autoSuspendAfterSeconds":{"$ref":"#/components/schemas/AutoSuspendAfterSeconds"},"desiredState":{"description":"Default: running.","enum":["running","stopped"],"type":"string"},"imageId":{"deprecated":true,"description":"Deprecated: `snapshotId` under its old name (both: they must be equal). The reply carries a `Deprecation` header.","type":"string"},"memMiB":{"description":"Like vcpus.","type":"integer"},"metadata":{"$ref":"#/components/schemas/Metadata"},"name":{"type":"string"},"onTimeout":{"$ref":"#/components/schemas/OnTimeout"},"persistentDiskGiB":{"description":"Like vcpus. Storage gets the fixed size, or a snapshot's storage size when that is larger.","type":"integer"},"pool":{"description":"Admins only: the host pool to place the sandbox in and no other (docs/hybrid-pools.md), \"default\" included. With snapshotId it must be the snapshot's pool. Absent: the user's pool assignment, else the server's placement.createPools.","type":"string"},"rootDiskGiB":{"description":"Like vcpus. System files get the template's size (or the snapshot's).","type":"integer"},"snapshotId":{"description":"Create from this snapshot of yours: its system files and storage (empty storage for a system-files-only snapshot).","type":"string"},"templateId":{"examples":["dev"],"type":"string"},"templateVersion":{"description":"Default: latest.","type":"string"},"timeoutSeconds":{"$ref":"#/components/schemas/TimeoutSeconds"},"vault":{"description":"False: the machine gets no Vault items, ever (docs/vault.md). Absent or true: it gets the caller's items at every boot and resume.","type":"boolean"},"vcpus":{"description":"Sizes are fixed (4 vCPUs, 8 GB memory by default; `limits.machineSpec`): 0 or absent, or exactly the size the sandbox gets; anything else is refused with `400 spec_not_allowed`.","type":"integer"}},"required":["name"],"type":"object"},"CreateOrcaConnectionRequest":{"properties":{"scope":{"description":"Orca device scope of the assertion: `runtime` (default, full access) or `mobile` (Orca's mobile method allowlist, push registration).","enum":["runtime","mobile"],"type":"string"}},"type":"object"},"CreateSSHTokenRequest":{"properties":{"name":{"description":"Optional display name; whitespace is trimmed, at most 100 UTF-8 bytes. Existing callers may omit it.","type":"string"},"ttlSeconds":{"description":"Default 604800 (7 days), max 2592000 (30 days).","maximum":2592000,"minimum":1,"type":"integer"}},"type":"object"},"CreateSnapshotRequest":{"properties":{"description":{"type":"string"},"name":{"description":"Unique among the caller's snapshots; 1-63 of a-z, 0-9, '-', starting with a letter or digit.","type":"string"},"rootOnly":{"default":false,"description":"Save the system files only, not the storage (`/data`).","type":"boolean"}},"required":["name"],"type":"object"},"CreatedAPIKey":{"properties":{"apiKey":{"$ref":"#/components/schemas/APIKey"},"key":{"description":"The secret","pattern":"^ibk_[a-z2-7]{52}$","shown only in this reply.":null,"type":"string"}},"required":["apiKey","key"],"type":"object"},"CreatedInvites":{"properties":{"invites":{"items":{"$ref":"#/components/schemas/Invite"},"type":"array"}},"required":["invites"],"type":"object"},"DeletedUser":{"properties":{"busyMachines":{"description":"Sandboxes with another operation in flight; deleted when it ends.","type":"integer"},"deletedAt":{"format":"date-time","type":"string"},"id":{"format":"int64","type":"integer"},"login":{"type":"string"},"machines":{"description":"Sandboxes whose delete operation was queued (or was already running).","type":"integer"},"snapshots":{"description":"Snapshots deleted.","type":"integer"}},"required":["id","login","deletedAt","machines","busyMachines","snapshots"],"type":"object"},"DeprecatedImage":{"allOf":[{"$ref":"#/components/schemas/Snapshot"},{"properties":{"official":{"const":false,"type":"boolean"},"owned":{"const":true,"type":"boolean"},"preinstalled":{"items":{"type":"object"},"maxItems":0,"type":"array"}},"required":["official","owned","preinstalled"],"type":"object"}],"deprecated":true,"description":"A snapshot as the deprecated /v1/images routes return it."},"DeprecatedImageList":{"deprecated":true,"properties":{"images":{"items":{"$ref":"#/components/schemas/DeprecatedImage"},"type":"array"}},"required":["images"],"type":"object"},"Desktop":{"properties":{"actions":{"description":"The computer-use MCP's last tool calls (at most 20, oldest first), as its proxy logged them.","items":{"$ref":"#/components/schemas/DesktopAction"},"type":"array"},"agentPausable":{"description":"The sandbox's computer-use MCP honors the control (its wrapper runs the server through the proxy). False on a sandbox made before the proxy existed: a takeover would not pause its agent, so clients should offer watching only.","type":"boolean"},"controlError":{"description":"The control file in the sandbox could not be written (its runtime directory is missing or not writable): takeover and handback cannot take effect. The reason, from the sandbox.","type":"string"},"controller":{"description":"Who drives the desktop: `agent` (the default; viewers watch) or `human` (a viewer took over, and the computer-use MCP refuses input until they hand back or 30 s pass without a call from them).","enum":["agent","human"],"type":"string"},"failed":{"description":"The last install failed; log holds its last lines.","type":"boolean"},"installed":{"type":"boolean"},"installing":{"type":"boolean"},"log":{"type":"string"},"running":{"description":"The VNC server answers: GET /v1/machines/{id}/vnc connects.","type":"boolean"},"yours":{"description":"The caller (its sign-in session or API key) is the viewer in control. Absent when false.","type":"boolean"}},"required":["installed","installing","running","failed","agentPausable"],"type":"object"},"DesktopAction":{"properties":{"args":{"additionalProperties":true,"description":"Its arguments, strings shortened to 80 characters.","type":"object"},"refused":{"description":"Refused because a human controlled the desktop; it never reached the tool.","type":"boolean"},"time":{"format":"date-time","type":"string"},"tool":{"description":"The MCP tool called (click, type_text, get_app_state, ...).","type":"string"}},"required":["time","tool"],"type":"object"},"DesktopRequest":{"properties":{"action":{"default":"status","enum":["status","install","start","takeover","handback"],"type":"string"},"height":{"description":"Screen height in pixels for start, 600-1600 (default 800).","type":"integer"},"width":{"description":"Screen width in pixels for start, 800-2560 (default 1280).","type":"integer"}},"type":"object"},"DesktopStream":{"properties":{"expiresAt":{"format":"date-time","type":"string"},"url":{"description":"The dashboard's full-screen viewer, `\u003cdashboard\u003e/desktop/{id}#token=...`; open it or embed it in an iframe.","type":"string"}},"required":["url","expiresAt"],"type":"object"},"DesktopStreamRequest":{"properties":{"ttlSeconds":{"default":3600,"description":"How long the link opens the desktop.","maximum":86400,"minimum":60,"type":"integer"},"viewOnly":{"default":true,"description":"The link only shows the desktop; the server drops the viewer's input.","type":"boolean"}},"type":"object"},"DeviceStart":{"properties":{"deviceCode":{"type":"string"},"expiresIn":{"description":"Seconds until the codes expire.","type":"integer"},"interval":{"description":"Seconds between polls.","type":"integer"},"userCode":{"type":"string"},"verificationUri":{"format":"uri","type":"string"}},"required":["deviceCode","userCode","verificationUri","interval","expiresIn"],"type":"object"},"DeviceStartRequest":{"properties":{"invite":{"description":"An invite code (`482913`, or an older `ibi_...` code) for an account that is not activated yet; spaces around it, spaces or dashes inside a numeric code and case do not matter.","maxLength":64,"type":"string"}},"type":"object"},"DeviceTokenRequest":{"properties":{"deviceCode":{"type":"string"}},"required":["deviceCode"],"type":"object"},"DrainFailure":{"properties":{"error":{"type":"string"},"machine":{"description":"Sandbox ID.","type":"string"}},"required":["machine","error"],"type":"object"},"Error":{"properties":{"code":{"description":"Machine-readable code. HTTP status per code is fixed.","enum":["unauthenticated","permission_denied","not_found","bad_request","operation_conflict","idempotency_conflict","machine_not_running","quota_exceeded","no_capacity","host_unavailable","metrics_unavailable","internal","authorization_pending","slow_down","expired_token","access_denied","invalid_grant","invite_required","invite_invalid","api_key_expired","api_key_revoked","insufficient_scope","orca_unsupported","runtime_not_ready","spec_not_allowed","vault_unavailable"],"type":"string"},"message":{"type":"string"},"operationId":{"description":"For `operation_conflict`, the active operation.","type":["string","null"]},"requestId":{"type":"string"},"retryable":{"description":"True if the same request may succeed later.","type":"boolean"}},"required":["code","message","requestId","retryable","operationId"],"type":"object"},"ErrorBody":{"properties":{"error":{"$ref":"#/components/schemas/Error"}},"required":["error"],"type":"object"},"ExecRequest":{"properties":{"cmd":{"description":"Shell command, run with `bash -lc`. At most 64 KiB, no NUL bytes.","maxLength":65536,"minLength":1,"type":"string"},"cwd":{"description":"Working directory (default HOME, `/data/home`).","maxLength":4096,"type":"string"},"envs":{"additionalProperties":{"type":"string"},"description":"Environment variables exported before the command (after the login profile). Names match `^[A-Za-z_][A-Za-z0-9_]*$`.","maxProperties":256,"type":"object"},"stdin":{"description":"Fed to the command, then EOF. At most 1 MiB.","maxLength":1048576,"type":"string"},"timeoutSeconds":{"description":"Default 60, max 600.","maximum":600,"minimum":1,"type":"integer"}},"required":["cmd"],"type":"object"},"ExecResult":{"properties":{"exitCode":{"description":"Exit status; 128+N if killed by standard signal N, otherwise 255 with `signal` set; 124 if timed out.","type":"integer"},"signal":{"description":"The signal that killed the command, as the sandbox named it (absent otherwise).","type":"string"},"stderr":{"description":"First 1 MiB of standard error.","type":"string"},"stdout":{"description":"First 1 MiB of standard output.","type":"string"},"timedOut":{"description":"The command ran past `timeoutSeconds` and was killed.","type":"boolean"},"truncated":{"description":"stdout or stderr was longer than 1 MiB and was cut.","type":"boolean"}},"required":["stdout","stderr","exitCode","timedOut","truncated"],"type":"object"},"FileEntry":{"properties":{"modTime":{"format":"date-time","type":"string"},"mode":{"type":"string"},"name":{"type":"string"},"size":{"description":"Bytes; 0 for a directory.","format":"int64","type":"integer"},"target":{"description":"A symbolic link's target, as stored.","type":"string"},"type":{"enum":["dir","file","symlink","other"],"type":"string"}},"required":["name","type","size","mode","modTime"],"type":"object"},"Files":{"properties":{"binary":{"description":"The file is not UTF-8 text; content is empty.","type":"boolean"},"content":{"description":"A text file's content (UTF-8).","type":"string"},"entries":{"description":"A directory's entries, directories first, then by name.","items":{"$ref":"#/components/schemas/FileEntry"},"type":"array"},"modTime":{"format":"date-time","type":"string"},"mode":{"description":"As ls -l, for example drwxr-xr-x.","type":"string"},"path":{"description":"The path read","type":"string","with symbolic links resolved.":null},"size":{"description":"Bytes; 0 for a directory.","format":"int64","type":"integer"},"truncated":{"description":"The directory has more entries","or the file more bytes":null,"than returned.":null,"type":"boolean"},"type":{"enum":["dir","file","other"],"type":"string"}},"required":["path","type","size","mode","modTime"],"type":"object"},"ForkMachineRequest":{"properties":{"autoSuspendAfterSeconds":{"allOf":[{"$ref":"#/components/schemas/AutoSuspendAfterSeconds"}],"description":"Absent: the default, 30 minutes (the source's setting is not copied); 0: off."},"desiredState":{"description":"Default: running.","enum":["running","stopped"],"type":"string"},"memMiB":{"description":"Like vcpus.","type":"integer"},"metadata":{"allOf":[{"$ref":"#/components/schemas/Metadata"}],"description":"Absent: the source's metadata; {}: none; otherwise replaces it."},"name":{"type":"string"},"onTimeout":{"$ref":"#/components/schemas/OnTimeout"},"persistentDiskGiB":{"description":"Like vcpus. Storage gets the fixed size, or the source's when that is larger.","type":"integer"},"rootDiskGiB":{"description":"Like vcpus. System files get the source's size.","type":"integer"},"timeoutSeconds":{"allOf":[{"$ref":"#/components/schemas/TimeoutSeconds"}],"description":"Absent: no timeout (the source's is not copied)."},"vault":{"description":"False: the fork gets no Vault items, ever. A fork of a machine without the Vault never gets them either.","type":"boolean"},"vcpus":{"description":"Sizes are fixed (`limits.machineSpec`, whatever the source's size): 0 or absent, or exactly the size the new sandbox gets; anything else is refused with `400 spec_not_allowed`.","type":"integer"}},"required":["name"],"type":"object"},"ForkRef":{"properties":{"machineId":{"type":"string"},"snapshotSeq":{"format":"int64","type":"integer"}},"required":["machineId","snapshotSeq"],"type":"object"},"GCSettings":{"properties":{"dryRun":{"type":"boolean"},"graceHours":{"type":"integer"},"intervalHours":{"type":"integer"}},"required":["dryRun","intervalHours","graceHours"],"type":"object"},"HostDrain":{"description":"A host's drain. total: the sandboxes on the host when the drain started; remaining: sandboxes (or local copies) still there, the failed ones included; moved: total - remaining.","properties":{"failed":{"description":"Sandboxes the drain gave up on, and why.","items":{"$ref":"#/components/schemas/DrainFailure"},"type":"array"},"host":{"type":"string"},"moved":{"type":"integer"},"remaining":{"type":"integer"},"state":{"enum":["idle","draining","drained","failed"],"type":"string"},"total":{"type":"integer"}},"required":["host","state","total","moved","remaining","failed"],"type":"object"},"HostLoadReport":{"description":"A host's measured load (node-agent sampler); absent from older node-agents.","properties":{"cpuBusy":{"description":"Busy share of all CPUs (0-1); absent before the node's second sample.","type":"number"},"load1":{"type":"number"},"memAvailableBytes":{"description":"MemAvailable.","type":"integer"},"memTotalBytes":{"type":"integer"},"oomKills":{"description":"OOM kills since boot.","type":"integer"},"psiCpuSome60":{"description":"PSI cpu some avg60, percent; absent without PSI.","type":"number"},"psiIoSome60":{"description":"PSI io some avg60, percent; absent without PSI.","type":"number"},"psiMemorySome60":{"description":"PSI memory some avg60, percent; absent without PSI.","type":"number"},"sampledAt":{"format":"date-time","type":"string"},"swapUsedBytes":{"type":"integer"},"vmMemoryMaxBytes":{"description":"memory.max of the VMs' cgroup; absent: no limit.","type":"integer"}},"required":["sampledAt","load1","memTotalBytes","memAvailableBytes","swapUsedBytes","oomKills"],"type":"object"},"HostReport":{"description":"A node-agent's last host report.","properties":{"cpuClass":{"description":"node.json `cpu_class`; absent when unset.","type":"string"},"cpuModel":{"type":"string"},"dataFreeGiB":{"type":"integer"},"dataTotalGiB":{"description":"Size of the data filesystem; absent from older node-agents.","type":"integer"},"firecrackerVersion":{"type":"string"},"freeMemMiB":{"type":"integer"},"freeSlots":{"type":"integer"},"hostname":{"type":"string"},"kernelRelease":{"type":"string"},"kernelSha256":{"type":"string"},"load":{"$ref":"#/components/schemas/HostLoadReport"},"machines":{"description":"Sandboxes the node knows.","type":"integer"},"memMiB":{"type":"integer"},"objectStore":{"type":"boolean"},"templates":{"items":{"type":"string"},"type":"array"},"templatesLocal":{"items":{"type":"string"},"type":"array"},"vcpus":{"type":"integer"}},"type":"object"},"HostTrust":{"properties":{"principals":{"items":{"type":"string"},"type":"array"},"publicKey":{"description":"SSH host certificate authority's public key, in authorized_keys format.","type":"string"},"type":{"enum":["ca"],"type":"string"}},"required":["type","publicKey","principals"],"type":"object"},"Invite":{"properties":{"code":{"description":"The code to hand out: 6 digits; codes made before 2026-10-13 are `ibi_` + 20 base32 characters.","pattern":"^([0-9]{6}|ibi_[a-z2-7]{20})$","type":"string"},"createdAt":{"format":"date-time","type":"string"},"createdBy":{"description":"The admin who made it, or for a personal code the account it belongs to.","format":"int64","type":"integer"},"createdByLogin":{"type":"string"},"expiresAt":{"description":"null: never.","format":"date-time","type":["string","null"]},"id":{"pattern":"^inv_[a-z2-7]{26}$","type":"string"},"maxUses":{"minimum":1,"type":"integer"},"note":{"type":"string"},"personal":{"description":"`createdBy`'s own code (`GET /v1/invites/mine`); absent: an admin made it.","type":"boolean"},"redemptions":{"description":"Who redeemed the code and when, oldest first.","items":{"$ref":"#/components/schemas/InviteRedemption"},"type":"array"},"revokedAt":{"format":"date-time","type":["string","null"]},"role":{"$ref":"#/components/schemas/InviteRole"},"state":{"description":"Revoked before expired before the use counts.","enum":["unused","partially_used","used","revoked","expired"],"type":"string"},"useCount":{"minimum":0,"type":"integer"}},"required":["id","code","note","role","maxUses","useCount","expiresAt","revokedAt","createdBy","createdAt","state","redemptions"],"type":"object"},"InviteList":{"properties":{"invites":{"items":{"$ref":"#/components/schemas/Invite"},"type":"array"}},"required":["invites"],"type":"object"},"InviteRedemption":{"properties":{"login":{"type":"string"},"redeemedAt":{"format":"date-time","type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["userId","login","redeemedAt"],"type":"object"},"InviteRole":{"description":"What redeeming grants: `user` activates the account; `admin` activates it and grants the administrator role, also to an account that is activated already.","enum":["user","admin"],"type":"string"},"JobRun":{"properties":{"error":{"type":"string"},"finishedAt":{"format":"date-time","type":"string"},"job":{"enum":["gc","usage"],"type":"string"},"startedAt":{"format":"date-time","type":"string"},"stats":{"additionalProperties":true,"description":"Job counters; durations as `…Seconds`.","type":"object"}},"required":["job","startedAt","finishedAt","stats"],"type":"object"},"Machine":{"description":"A sandbox.","properties":{"archiveAt":{"description":"When the sandbox is stopped if it is running (set at creation with `archiveSeconds`): its files are kept and a start brings it back. Resuming does not clear it; a sandbox that is not running when it passes only has it cleared. Absent: none.","format":"date-time","type":["string","null"]},"autoSuspendAfterSeconds":{"description":"The running sandbox is paused once it has been idle this many seconds (no agent working, no terminal input or output, little CPU); null: off. Pause, resume, stop and start keep it.","type":["integer","null"]},"autoSuspendedAt":{"description":"When the sandbox was queued to pause for being idle; a successful automatic resume or its next explicit operation clears it. Absent: none.","format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"},"desiredState":{"$ref":"#/components/schemas/MachineState"},"forkedFrom":{"$ref":"#/components/schemas/ForkRef"},"id":{"type":"string"},"image":{"allOf":[{"$ref":"#/components/schemas/SnapshotRef"}],"deprecated":true,"description":"Deprecated: `snapshot`, under its old name (same value)."},"lastError":{"$ref":"#/components/schemas/Error"},"metadata":{"$ref":"#/components/schemas/Metadata"},"name":{"type":"string"},"onTimeout":{"$ref":"#/components/schemas/OnTimeout"},"operationId":{"description":"The active operation, if any.","type":["string","null"]},"revision":{"description":"Increases with each accepted change.","format":"int64","type":"integer"},"runtime":{"$ref":"#/components/schemas/Runtime"},"snapshot":{"$ref":"#/components/schemas/SnapshotRef"},"spec":{"$ref":"#/components/schemas/MachineSpec"},"state":{"$ref":"#/components/schemas/MachineState"},"storage":{"$ref":"#/components/schemas/Storage"},"template":{"$ref":"#/components/schemas/TemplateRef"},"timeoutAt":{"description":"When the sandbox is paused or deleted (see `onTimeout`); null for no timeout.","format":"date-time","type":["string","null"]},"updatedAt":{"format":"date-time","type":"string"},"vault":{"description":"The machine gets the owner's Vault items at every boot and resume (docs/vault.md). False, once set, is for good.","type":"boolean"}},"required":["id","name","desiredState","state","revision","operationId","template","spec","storage","runtime","timeoutAt","onTimeout","autoSuspendAfterSeconds","metadata","vault","createdAt","updatedAt"],"type":"object"},"MachineList":{"properties":{"machines":{"items":{"$ref":"#/components/schemas/Machine"},"type":"array"},"nextCursor":{"description":"Pass as `cursor` for the next page; null on the last page.","type":["string","null"]}},"required":["machines","nextCursor"],"type":"object"},"MachineMetric":{"description":"One sample, measured on the host. Bytes throughout.","properties":{"cpuCount":{"description":"vCPUs.","type":"integer"},"cpuUsedPct":{"description":"CPU time used over the sandbox's vCPUs, 0-100.","type":"number"},"diskTotal":{"description":"System disk plus /data disk size.","format":"int64","type":"integer"},"diskUsed":{"description":"Not reported yet; absent.","format":"int64","type":"integer"},"memTotal":{"description":"Memory size.","format":"int64","type":"integer"},"memUsed":{"description":"Guest memory touched (it drops only when memory is reclaimed).","format":"int64","type":"integer"},"timestamp":{"format":"date-time","type":"string"}},"required":["timestamp","cpuUsedPct","cpuCount","memUsed","memTotal","diskTotal"],"type":"object"},"MachineMetrics":{"properties":{"metrics":{"description":"Oldest first.","items":{"$ref":"#/components/schemas/MachineMetric"},"type":"array"}},"required":["metrics"],"type":"object"},"MachineSpec":{"description":"A sandbox's size.","properties":{"memMiB":{"description":"Memory in MiB.","type":"integer"},"persistentDiskGiB":{"description":"Storage (`/data`) size in GiB.","type":"integer"},"rootDiskGiB":{"description":"System files size in GiB; absent: the template's.","type":"integer"},"vcpus":{"type":"integer"}},"required":["vcpus","memMiB","persistentDiskGiB"],"type":"object"},"MachineState":{"enum":["creating","starting","running","stopping","stopped","suspending","suspended","resuming","deleting","deleted","error"],"type":"string"},"MachineUsage":{"properties":{"diskGiBSeconds":{"description":"Disk size (system disk plus /data, current size) times the seconds the sandbox existed in the window.","format":"int64","type":"integer"},"from":{"format":"date-time","type":"string"},"memGiBSeconds":{"description":"Memory GiB times running seconds.","type":"number"},"runningSeconds":{"description":"Seconds the sandbox was running in the window.","format":"int64","type":"integer"},"to":{"format":"date-time","type":"string"},"vcpuSeconds":{"description":"vCPUs times running seconds.","format":"int64","type":"integer"}},"required":["from","to","runningSeconds","vcpuSeconds","memGiBSeconds","diskGiBSeconds"],"type":"object"},"MachineVault":{"properties":{"applied":{"description":"The last write into the machine; null: none yet.","oneOf":[{"$ref":"#/components/schemas/VaultApplied"},{"type":"null"}]},"enabled":{"description":"The machine takes the Vault.","type":"boolean"},"items":{"description":"The items that apply with the machine's current metadata (none when enabled is false).","items":{"$ref":"#/components/schemas/MachineVaultItem"},"type":"array"}},"required":["enabled","items","applied"],"type":"object"},"MachineVaultItem":{"properties":{"kind":{"enum":["env","file"],"type":"string"},"name":{"type":"string"},"path":{"type":"string"}},"required":["name","kind"],"type":"object"},"Me":{"properties":{"activated":{"description":"This account passed the invite gate (admins always have). When `inviteRequired` and not `activated`, every route but this one, `POST /v1/invites/redeem` and `POST /v1/auth/logout` answers `403 invite_required`.","type":"boolean"},"admin":{"description":"The user is an admin and may use the admin-only endpoints (`/v1/admin`).","type":"boolean"},"adminUrl":{"description":"Admins only: the origin of the admin console when it has its own (the server's `adminUrl`). Absent, the console is `/admin/` on the dashboard's origin.","format":"uri","type":"string"},"allowedSpecs":{"description":"One entry: the size every new sandbox gets (the server's `limits.machineSpec`, 4 vCPUs and 8 GB memory by default; no `rootDiskGiB`: the template's). A request naming another size is refused with `spec_not_allowed`, admins included.","items":{"$ref":"#/components/schemas/MachineSpec"},"type":"array"},"avatarUrl":{"format":"uri","type":"string"},"csrfToken":{"description":"Browser sessions only. Send it as `X-Instabox-CSRF` on every request that is not GET or HEAD.","type":"string"},"inviteRequired":{"description":"This server gates new accounts behind an invite code (docs/invites.md).","type":"boolean"},"login":{"description":"GitHub login at last sign-in (display only).","type":"string"},"name":{"type":"string"},"userId":{"description":"Instabox user ID.","format":"int64","type":"integer"}},"required":["userId","login"],"type":"object"},"Metadata":{"additionalProperties":{"type":"string"},"description":"String labels. At most 32 keys of 1-64 characters `[A-Za-z0-9_.-]`, values of at most 512 characters, 8 KiB as JSON.","type":"object"},"MetricsQueryResult":{"description":"The metrics store's answer in the Prometheus HTTP API format, unchanged: `data.resultType` is `vector` (instant) or `matrix` (range); each result has `metric` (labels) and `value` `[unixSeconds, \"value\"]` or `values` (a list of those).","properties":{"data":{"properties":{"result":{"items":{"additionalProperties":true,"type":"object"},"type":"array"},"resultType":{"enum":["vector","matrix","scalar","string"],"type":"string"}},"type":"object"},"error":{"type":"string"},"status":{"enum":["success","error"],"type":"string"}},"required":["status"],"type":"object"},"MovePoolRequest":{"properties":{"name":{"description":"A snapshot copy's name (default \u003cname\u003e-\u003cpool\u003e).","type":"string"},"pool":{"description":"The target pool; \"default\" for the default pool.","type":"string"}},"required":["pool"],"type":"object"},"MovePoolResult":{"properties":{"chunks":{"type":"integer"},"copiedBytes":{"format":"int64","type":"integer"},"copiedChunks":{"description":"Chunks the target store did not have yet.","type":"integer"},"id":{"description":"The sandbox or snapshot (a copy's new ID).","type":"string"},"pool":{"type":"string"}},"required":["id","pool","chunks","copiedChunks","copiedBytes"],"type":"object"},"MyInvite":{"description":"The account's own invite code (a user code that lets three people in, no expiry).","properties":{"code":{"description":"The code to hand out: 6 digits; codes made before 2026-10-13 are `ibi_` + 20 base32 characters.","pattern":"^([0-9]{6}|ibi_[a-z2-7]{20})$","type":"string"},"createdAt":{"format":"date-time","type":"string"},"maxUses":{"description":"3.","minimum":1,"type":"integer"},"redeemers":{"description":"Who joined with the code and when, oldest first.","items":{"$ref":"#/components/schemas/MyInviteRedeemer"},"type":"array"},"state":{"description":"As on the admin console (revoked before expired before the use counts).","enum":["unused","partially_used","used","revoked","expired"],"type":"string"},"useCount":{"minimum":0,"type":"integer"}},"required":["code","state","useCount","maxUses","createdAt","redeemers"],"type":"object"},"MyInviteRedeemer":{"properties":{"login":{"type":"string"},"redeemedAt":{"format":"date-time","type":"string"}},"required":["login","redeemedAt"],"type":"object"},"ObjectClientStats":{"properties":{"bytesDown":{"format":"int64","type":"integer"},"bytesUp":{"format":"int64","type":"integer"},"failures":{"format":"int64","type":"integer"},"requests":{"format":"int64","type":"integer"},"retries":{"format":"int64","type":"integer"},"timeouts":{"format":"int64","type":"integer"}},"required":["requests","retries","failures","timeouts","bytesUp","bytesDown"],"type":"object"},"OnTimeout":{"description":"What happens at `timeoutAt`: `pause` pauses a running sandbox, `kill` deletes it. Default: pause.","enum":["pause","kill"],"type":"string"},"Operation":{"description":"The progress of a change to a sandbox (`suspend` is pause).","properties":{"createdAt":{"format":"date-time","type":"string"},"error":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"type":"null"}]},"id":{"type":"string"},"machine":{"allOf":[{"$ref":"#/components/schemas/Machine"}],"description":"The sandbox as it is when `GET /v1/operations/{id}` is read. Included when the operation `succeeded`, the sandbox still exists, and the caller may read sandboxes (`machines:read` for API keys). Later operations can change it; it is not a record of the state at completion. Absent otherwise, and from older servers."},"machineId":{"type":"string"},"phase":{"description":"Free text progress, e.g. scheduling, booting, waiting_ready, snapshotting.","type":"string"},"state":{"description":"`succeeded` and `failed` are terminal.","enum":["queued","running","succeeded","failed"],"type":"string"},"trigger":{"description":"Set when instabox started the operation itself: `idle` (paused when idle), `request` (wake on use), `timeout`, `archive`, `drain` (maintenance by an admin) or `user_delete` (an admin deleted the owner). Absent: a client request.","enum":["idle","timeout","archive","drain","request","user_delete"],"type":"string"},"type":{"enum":["create","start","stop","suspend","resume","delete"],"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","machineId","type","state","error","createdAt","updatedAt"],"type":"object"},"OrcaConnection":{"properties":{"assertion":{"description":"Compact JWS (EdDSA) for the runtime's `e2ee_auth`.","type":"string"},"endpoint":{"description":"`wss://\u003cserver\u003e/v1/tunnels/ibt_…`; single use.","type":"string"},"expiresAt":{"description":"When the endpoint's ticket expires.","format":"date-time","type":"string"},"machineId":{"type":"string"},"scope":{"enum":["runtime","mobile"],"type":"string"},"serverPublicKey":{"description":"The runtime's NaCl box public key (base64).","type":"string"}},"required":["machineId","endpoint","assertion","serverPublicKey","scope","expiresAt"],"type":"object"},"OrcaKey":{"properties":{"crv":{"enum":["Ed25519"],"type":"string"},"kid":{"type":"string"},"kty":{"enum":["OKP"],"type":"string"},"x":{"description":"base64url public key.","type":"string"}},"required":["kid","kty","crv","x"],"type":"object"},"OrcaKeys":{"properties":{"issuer":{"description":"The `iss` of assertions (the server's public URL).","type":"string"},"keys":{"items":{"$ref":"#/components/schemas/OrcaKey"},"type":"array"}},"required":["issuer","keys"],"type":"object"},"PublicPort":{"properties":{"createdAt":{"format":"date-time","type":"string"},"expiresAt":{"deprecated":true,"description":"Always null (public ports do not expire); kept for older in-guest instabox-port agents.","type":"null"},"id":{"description":"The URL's first DNS label.","type":"string"},"machineId":{"type":"string"},"name":{"type":"string"},"port":{"type":"integer"},"url":{"description":"Empty when public ports are not configured.","format":"uri","type":"string"}},"required":["id","machineId","port","name","url","createdAt"],"type":"object"},"PublicPortList":{"properties":{"enabled":{"description":"Whether the server has public ports configured.","type":"boolean"},"ports":{"items":{"$ref":"#/components/schemas/PublicPort"},"type":"array"}},"required":["enabled","ports"],"type":"object"},"PublishPortRequest":{"properties":{"name":{"description":"Omitted: the current name, or \"Port N\" for a new public port.","maxLength":100,"type":"string"},"port":{"description":"Desktop port 5901 and Orca port 6768 (plus the configured Orca port) are reserved.","maximum":65535,"minimum":1024,"type":"integer"}},"required":["port"],"type":"object"},"PutVaultItemRequest":{"properties":{"kind":{"enum":["env","file"],"type":"string"},"path":{"description":"File items only: the target, relative to the home directory.","maxLength":255,"type":"string"},"selector":{"additionalProperties":{"type":"string"},"description":"Absent or {}: the item applies to every machine.","type":"object"},"value":{"description":"UTF-8 text without NUL bytes, 1 byte to 64 KiB, stored as given.","maxLength":65536,"minLength":1,"type":"string"}},"required":["kind","value"],"type":"object"},"QuotaLimits":{"description":"A user's limits: live sandboxes (any state), live snapshots, and stored GiB (new sandboxes, forks and snapshots are refused while over it).","properties":{"maxMachines":{"type":"integer"},"maxSnapshots":{"type":"integer"},"maxStoredGiB":{"type":"integer"}},"required":["maxMachines","maxSnapshots","maxStoredGiB"],"type":"object"},"RedeemInviteRequest":{"properties":{"code":{"description":"An invite code (`482913`, or an older `ibi_...` code); spaces around it, spaces or dashes inside a numeric code (`482 913`) and case do not matter.","maxLength":64,"type":"string"}},"required":["code"],"type":"object"},"RefreshRequest":{"properties":{"refreshToken":{"type":"string"}},"required":["refreshToken"],"type":"object"},"ResumeMachineRequest":{"properties":{"vault":{"description":"False: the machine gets no Vault items from now on (one-way); the write after this resume removes what the Vault wrote.","type":"boolean"}},"type":"object"},"Runtime":{"properties":{"processesPreserved":{"description":"Set after a start or resume. False means the paused programs could not be brought back and the sandbox booted fresh (files are kept).","type":"boolean"},"ready":{"description":"True only when the sandbox is running and reports all checks passing.","type":"boolean"}},"required":["ready"],"type":"object"},"SSHToken":{"properties":{"command":{"description":"`ssh -p \u003cport\u003e \u003ctoken\u003e@\u003chost\u003e` (without `-p` on port 22).","type":"string"},"expiresAt":{"format":"date-time","type":"string"},"host":{"type":"string"},"hostKeyFingerprint":{"description":"SHA256 fingerprint of the SSH server's host key, which ssh asks to confirm on the first connection.","type":"string"},"id":{"description":"Connection ID (`cn_…`) of the token's grant.","type":"string"},"machineId":{"type":"string"},"moshCommand":{"description":"The same login with mosh: `mosh --ssh=\"ssh -p \u003cport\u003e\" \u003ctoken\u003e@\u003chost\u003e` (`mosh \u003ctoken\u003e@\u003chost\u003e` on port 22). Absent when mosh is not available.","type":"string"},"name":{"type":"string"},"port":{"type":"integer"},"token":{"description":"`ibssh_` and 26 lowercase base32 characters (128 bits): the SSH user name. Shown only here.","type":"string"}},"required":["id","name","machineId","host","port","token","command","expiresAt","hostKeyFingerprint"],"type":"object"},"SSHTokenInfo":{"properties":{"createdAt":{"format":"date-time","type":"string"},"expiresAt":{"format":"date-time","type":"string"},"id":{"type":"string"},"name":{"type":"string"},"revokedAt":{"format":"date-time","type":"string"}},"required":["id","name","createdAt","expiresAt"],"type":"object"},"SSHTokenList":{"properties":{"tokens":{"items":{"$ref":"#/components/schemas/SSHTokenInfo"},"type":"array"}},"required":["tokens"],"type":"object"},"SSHTokensRevoked":{"properties":{"revoked":{"description":"Tokens that were still valid.","type":"integer"}},"required":["revoked"],"type":"object"},"Session":{"properties":{"createdAt":{"format":"date-time","type":"string"},"current":{"description":"The session making this request.","type":"boolean"},"expiresAt":{"format":"date-time","type":"string"},"id":{"description":"Revoke handle (`ss_…`).","type":"string"},"kind":{"enum":["cli","browser"],"type":"string"},"lastUsedAt":{"description":"Last browser use, or the last token refresh of a CLI session.","format":"date-time","type":"string"}},"required":["id","kind","createdAt","lastUsedAt","expiresAt","current"],"type":"object"},"SessionList":{"properties":{"sessions":{"items":{"$ref":"#/components/schemas/Session"},"type":"array"}},"required":["sessions"],"type":"object"},"Snapshot":{"description":"A saved copy of a sandbox (software and files). New sandboxes can be created from it.","properties":{"createdAt":{"format":"date-time","type":"string"},"dataSizeBytes":{"description":"Size of the saved storage (`/data`); absent for system-files-only snapshots. Sandboxes created from it get at least this much storage.","format":"int64","type":"integer"},"defaultSpec":{"allOf":[{"$ref":"#/components/schemas/MachineSpec"}],"description":"The size recorded for this snapshot: the source sandbox's vCPUs and memory (older snapshots: the template's `defaultSpec`), its storage size and its system files size. A sandbox created from it gets the fixed vCPUs and memory regardless, and storage of at least this size. Absent when its template is unknown."},"description":{"type":"string"},"error":{"description":"Why a `failed` snapshot failed.","type":"string"},"exclusiveBytes":{"description":"Stored bytes only this snapshot references (last usage run; absent before one ran).","format":"int64","type":"integer"},"id":{"description":"`sn_...` (`im_...` for older snapshots).","type":"string"},"name":{"type":"string"},"rootSizeBytes":{"description":"Size of the saved system files.","format":"int64","type":"integer"},"sourceMachineId":{"type":"string"},"sourceSnapshotSeq":{"description":"The source sandbox's saved state (`storage.snapshotSeq`) this snapshot was taken from; 0 for a live snapshot of a sandbox that was never stopped.","format":"int64","type":"integer"},"status":{"description":"`ready` can be used. A snapshot of a running sandbox is `pending` while it uploads (its sizes are the sandbox's until then), then `ready` or `failed`.","enum":["pending","ready","failed"],"type":"string"},"template":{"$ref":"#/components/schemas/TemplateRef"}},"required":["id","name","sourceMachineId","sourceSnapshotSeq","template","rootSizeBytes","createdAt","status"],"type":"object"},"SnapshotList":{"properties":{"snapshots":{"items":{"$ref":"#/components/schemas/Snapshot"},"type":"array"}},"required":["snapshots"],"type":"object"},"SnapshotRef":{"properties":{"id":{"type":"string"}},"required":["id"],"type":"object"},"StartMachineRequest":{"properties":{"host":{"description":"Admin only: place the sandbox on this host. While `storage.synced` is false the sandbox can only start on its current host; another host gets `409 operation_conflict`.","type":"string"},"vault":{"description":"False: the sandbox gets no Vault items from now on (one-way).","type":"boolean"}},"type":"object"},"StopMachineRequest":{"properties":{"timeoutSeconds":{"description":"How long to wait for a clean shutdown before the sandbox is forced off.","type":"integer"}},"type":"object"},"Storage":{"description":"The sandbox's storage (`/data`, where HOME lives).","properties":{"mountPath":{"examples":["/data"],"type":"string"},"persistent":{"type":"boolean"},"sizeGiB":{"type":"integer"},"snapshotSeq":{"description":"Sequence number of the sandbox's latest saved state; absent before the first stop or pause.","format":"int64","type":"integer"},"synced":{"description":"False while the latest state of a stopped or paused sandbox is still being saved: for a few seconds after a stop or pause, or longer if saving fails (it is retried). A snapshot or fork waits until it is true.","type":"boolean"}},"required":["sizeGiB","mountPath","persistent","synced"],"type":"object"},"StoragePool":{"properties":{"bucket":{"type":"string"},"client":{"oneOf":[{"$ref":"#/components/schemas/ObjectClientStats"},{"type":"null"}]},"endpoint":{"type":"string"},"pool":{"type":"string"},"prefix":{"type":"string"}},"required":["pool","endpoint","bucket","client"],"type":"object"},"Template":{"description":"A template version a sandbox can start from. Versions never change.","properties":{"arch":{"examples":["x86_64"],"type":"string"},"capabilities":{"description":"`identity-reset`: its sandboxes can be snapshotted and forked. `root-grow`: an admin can grow its sandboxes' sizes. `orca`: its sandboxes run an Orca runtime (`POST /v1/machines/{id}/orca-connections`). `activity`: it ships activity probes for its agents, so pausing when idle (`autoSuspendAfterSeconds`) sees them working; without it, terminal and CPU activity alone count.","items":{"enum":["identity-reset","root-grow","orca","activity"],"type":"string"},"type":"array"},"defaultSpec":{"$ref":"#/components/schemas/MachineSpec"},"description":{"type":"string"},"digest":{"description":"sha256:\u003crootfs sha\u003e","type":"string"},"hidden":{"description":"This template is no longer offered for new sandboxes: a create naming it is refused. It is still listed so that sandboxes and snapshots built on it keep showing its size and capabilities.","type":"boolean"},"id":{"examples":["herdr"],"type":"string"},"preboot":{"description":"New sandboxes of this template version start faster: they come from an already booted copy, one per `prebootSpecs` entry (absent: its `defaultSpec`). Otherwise, or when no such copy is ready, the sandbox boots as usual.","type":"boolean"},"prebootSpecs":{"description":"The sizes `preboot` applies to. Absent: `defaultSpec` only.","items":{"$ref":"#/components/schemas/MachineSpec"},"type":"array"},"runtime":{"additionalProperties":{"type":"string"},"description":"Software baked into the template, e.g. {\"herdr\":\"0.9.3\"}.","type":"object"},"version":{"examples":["2026-10-02.1"],"type":"string"}},"required":["id","version","digest","arch","defaultSpec"],"type":"object"},"TemplateList":{"properties":{"templates":{"items":{"$ref":"#/components/schemas/Template"},"type":"array"}},"required":["templates"],"type":"object"},"TemplateRef":{"properties":{"digest":{"type":"string"},"id":{"type":"string"},"version":{"type":"string"}},"required":["id","version","digest"],"type":"object"},"TimeoutSeconds":{"description":"Lifetime from now in seconds; the sandbox gets `timeoutAt` = now + this.","maximum":86400,"minimum":60,"type":"integer"},"Tokens":{"properties":{"accessExpiresAt":{"format":"date-time","type":"string"},"accessToken":{"type":"string"},"refreshExpiresAt":{"format":"date-time","type":"string"},"refreshToken":{"type":"string"}},"required":["accessToken","accessExpiresAt","refreshToken","refreshExpiresAt"],"type":"object"},"UpdateHostRequest":{"properties":{"enabled":{"type":"boolean"}},"required":["enabled"],"type":"object"},"UpdateMachineRequest":{"properties":{"autoSuspendAfterSeconds":{"description":"120-86400: pause the sandbox after this many idle seconds; 0: off; absent: unchanged.","type":"integer"},"metadata":{"additionalProperties":{"type":"string"},"description":"Replaces the metadata (same limits as `Metadata`); {} clears it; null or absent: unchanged.","type":["object","null"]},"onTimeout":{"$ref":"#/components/schemas/OnTimeout"},"persistentDiskGiB":{"description":"Admin only: new storage (`/data`) size; 0 or absent: unchanged. A smaller size, or a template that cannot grow disks, is `400 bad_request`; otherwise non-admins get `400 spec_not_allowed`.","type":"integer"},"rootDiskGiB":{"description":"Admin only: new system files size; 0 or absent: unchanged. A smaller size, or a template that cannot grow disks, is `400 bad_request`; otherwise non-admins get `400 spec_not_allowed`.","type":"integer"},"timeoutSeconds":{"description":"60-86400: timeoutAt = now + this; 0: clear the timeout; absent: unchanged.","type":"integer"},"vault":{"description":"False turns the Vault off for the sandbox, in any state, for good; true on a sandbox without it is `400 bad_request`; absent: unchanged.","type":"boolean"}},"type":"object"},"UpdateUserRequest":{"description":"At least one field. maxMachines, maxSnapshots and maxStoredGiB are the user's quota overrides: 0 clears the override (the server.json limit applies), positive sets it (at most 1000, 10000 and 102400). pool: the user's host pool assignment (docs/hybrid-pools.md); \"\" clears it. A field that is absent or null is left unchanged.","properties":{"maxMachines":{"maximum":1000,"minimum":0,"type":["integer","null"]},"maxSnapshots":{"maximum":10000,"minimum":0,"type":["integer","null"]},"maxStoredGiB":{"maximum":102400,"minimum":0,"type":["integer","null"]},"pool":{"type":["string","null"]}},"type":"object"},"UpdateVaultItemRequest":{"properties":{"selector":{"additionalProperties":{"type":"string"},"description":"The new selector; {} clears it.","type":"object"}},"required":["selector"],"type":"object"},"Usage":{"properties":{"computedAt":{"description":"When the byte figures were measured (about hourly); null before your first measurement.","format":"date-time","type":["string","null"]},"exclusiveBytes":{"description":"The part of storedBytes no other user shares.","format":"int64","type":"integer"},"images":{"deprecated":true,"description":"Deprecated: `snapshots` (same value).","type":"integer"},"limits":{"$ref":"#/components/schemas/UsageLimits"},"logicalBytes":{"description":"Full size of your sandboxes and snapshots.","format":"int64","type":"integer"},"machines":{"description":"Your sandboxes, in any state.","type":"integer"},"snapshots":{"description":"Your snapshots.","type":"integer"},"storage":{"allOf":[{"$ref":"#/components/schemas/UsageStorage"}],"description":"storedBytes by sandbox. Absent before your first measurement, and from older servers."},"storedBytes":{"description":"What they take up once compressed, with shared data counted once and the templates' own content (an unchanged system disk) not counted: what you wrote on the system disk counts. Paused sandboxes' saved memory does not count. Checked against `limits.maxStoredGiB`.","format":"int64","type":"integer"}},"required":["computedAt","logicalBytes","storedBytes","exclusiveBytes","machines","snapshots","images","limits"],"type":"object"},"UsageLimits":{"description":"Your limits. An admin may change maxMachines per user.","properties":{"liveSnapshots":{"description":"Snapshots of running sandboxes are allowed (some may still be refused until the server is fully updated). Absent: they are not.","type":"boolean"},"machineSpec":{"allOf":[{"$ref":"#/components/schemas/MachineSpec"}],"description":"The size every new sandbox gets (4 vCPUs, 8 GB memory by default; no rootDiskGiB: the template's). Absent from servers before fixed sizes."},"maxImages":{"deprecated":true,"description":"Deprecated: `maxSnapshots` (same value).","type":"integer"},"maxMachines":{"description":"Sandboxes in any state, checked on create and fork (default 5). There are no vCPU or memory quotas.","type":"integer"},"maxSnapshots":{"type":"integer"},"maxStoredGiB":{"description":"Stored data limit (default 200), checked against storedBytes when you create a sandbox, snapshot or fork.","type":"integer"},"vault":{"allOf":[{"$ref":"#/components/schemas/VaultLimits"}],"description":"The Vault's limits. Absent: this server has no Vault."}},"required":["maxMachines","maxSnapshots","maxStoredGiB","maxImages"],"type":"object"},"UsageMachineStorage":{"properties":{"diskBytes":{"description":"Its saved disks' share of storedBytes: `/data` and what was written on the system disk (0 until its disks are first saved, on stop, pause or snapshot).","format":"int64","type":"integer"},"id":{"type":"string"},"name":{"type":"string"},"sizeBytes":{"description":"Size of its storage (`/data`).","format":"int64","type":"integer"},"snapshotBytes":{"description":"Their share.","format":"int64","type":"integer"},"snapshots":{"description":"Its snapshots.","type":"integer"},"state":{"type":"string"}},"required":["id","name","state","sizeBytes","diskBytes","snapshots","snapshotBytes"],"type":"object"},"UsageStorage":{"description":"storedBytes by sandbox, from the same measurement. Data that several of your sandboxes and snapshots share is split evenly between them, so the figures sum to storedBytes.","properties":{"deletedMachineSnapshotBytes":{"description":"Their share of storedBytes.","format":"int64","type":"integer"},"deletedMachineSnapshots":{"description":"Snapshots whose sandbox is deleted.","type":"integer"},"machines":{"description":"Your sandboxes.","items":{"$ref":"#/components/schemas/UsageMachineStorage"},"type":"array"}},"required":["machines","deletedMachineSnapshots","deletedMachineSnapshotBytes"],"type":"object"},"UsageTotals":{"properties":{"bucketChunkBytes":{"description":"All chunks in the bucket","format":"int64","from the last usage run.":null,"type":["integer","null"]},"computedAt":{"format":"date-time","type":["string","null"]},"exclusiveBytes":{"format":"int64","type":"integer"},"logicalBytes":{"format":"int64","type":"integer"},"storedBytes":{"description":"Per-user stored bytes summed (template chunks not counted).","format":"int64","type":"integer"},"users":{"type":"integer"}},"required":["computedAt","users","logicalBytes","storedBytes","exclusiveBytes","bucketChunkBytes"],"type":"object"},"VaultApplied":{"description":"One write of the Vault into a machine.","properties":{"at":{"format":"date-time","type":"string"},"env":{"description":"The variables exported.","items":{"type":"string"},"type":"array"},"error":{"description":"Why the write failed","type":"string","when it did.":null},"files":{"description":"The files written (or, when the write failed, those of the last write, kept for the next reconciliation).","items":{"$ref":"#/components/schemas/VaultFile"},"type":"array"},"kept":{"description":"Files that no longer apply but were left (changed in the machine since, or not writable).","items":{"$ref":"#/components/schemas/VaultKept"},"type":"array"},"removed":{"description":"Files of an earlier write removed because they no longer apply.","items":{"type":"string"},"type":"array"}},"required":["at","env","files"],"type":"object"},"VaultFile":{"properties":{"path":{"type":"string"},"sha256":{"description":"Hex sha256 of the content written.","type":"string"}},"required":["path","sha256"],"type":"object"},"VaultItem":{"description":"A Vault item (docs/vault.md). Its value is never returned.","properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"description":"`vi_…`","type":"string"},"kind":{"description":"`env`: a variable exported to every login shell of the machine from a tmpfs file. `file`: a file at `path` on the disk.","enum":["env","file"],"type":"string"},"machines":{"description":"The caller's live machines the item applies to (selector matches, Vault not off).","type":"integer"},"name":{"description":"Unique per user. An env item's name is the variable's name.","type":"string"},"path":{"description":"File items: the target, relative to the home directory.","type":"string"},"selector":{"additionalProperties":{"type":"string"},"description":"Labels a machine's metadata must contain for the item to apply; empty: every machine. Same limits as `Metadata`.","type":"object"},"size":{"description":"The value's size in bytes.","type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","name","kind","selector","size","machines","createdAt","updatedAt"],"type":"object"},"VaultItemList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/VaultItem"},"type":"array"}},"required":["items"],"type":"object"},"VaultKept":{"properties":{"path":{"type":"string"},"reason":{"type":"string"}},"required":["path","reason"],"type":"object"},"VaultLimits":{"properties":{"maxBytes":{"description":"Bytes of all values per user (server default 262144).","type":"integer"},"maxItems":{"description":"Items per user (server default 100).","type":"integer"}},"required":["maxItems","maxBytes"],"type":"object"}},"securitySchemes":{"apiKey":{"description":"API key (`ibk_...`) with scopes; also accepted as `Authorization: Bearer ibk_...`.\nAccepted on sandbox, snapshot, operation, template and usage routes per scope\n(see API keys), never on key management, sessions, logout or admin routes.\n","in":"header","name":"X-API-Key","type":"apiKey"},"bearerAuth":{"description":"Opaque instabox access token (1 hour). Refresh with `/v1/auth/refresh` (refresh token valid 30 days, rotates on use).","scheme":"bearer","type":"http"},"portAgent":{"description":"Port ibpv_...; accepted only on this sandbox's port list, publish and unpublish routes, while it runs.","in":"header","name":"Authorization","type":"apiKey"},"sessionCookie":{"description":"Dashboard browser session from `/auth/github/login` (12 hours idle, 7 days at\nmost). Used only when no Authorization header is sent. Requests other than\nGET and HEAD must send `X-Instabox-CSRF` (from `GET /v1/me`) and an `Origin`\nequal to the server's `publicUrl`; otherwise `403 permission_denied`.\n","in":"cookie","name":"instabox_session","type":"apiKey"}}},"info":{"description":"The instabox API creates and manages sandboxes: Linux computers in the\ncloud that keep their files and software, for coding agents such as\nClaude Code, Codex and OpenCode. It also issues short-lived SSH\ncredentials for reaching them. The `instabox` CLI, the dashboard, herdr,\ncmux and Orca all use it. The Go types in `pkg/instaboxapi` are the\nsource of truth; this document mirrors them.\n\nThe API is older than the name \"sandbox\". Paths, fields and scopes call a\nsandbox a machine (`/v1/machines`, `machineId`, `machines:read`), and\npausing is `suspend` and `resume`. The text here says \"sandbox\" and\n\"pause\"; code keeps the API's names.\n\n## Sandboxes\n\n- Every new sandbox gets the same size: 4 vCPUs and 8 GB of memory\n  (`limits.machineSpec` in `GET /v1/usage`). Leave `vcpus`, `memMiB`,\n  `persistentDiskGiB` and `rootDiskGiB` out of requests; a request that\n  names a different size gets `400 spec_not_allowed`. Only admins can\n  resize an existing sandbox.\n- A sandbox has **system files** (the operating system and installed\n  software; \"root disk\" in field names) and **storage** (`/data`, which\n  holds HOME at `/data/home`). Both are kept across stop, pause and start.\n- Each user can have up to 5 sandboxes in any state (stopped and paused\n  ones count) and 200 GB of stored data across sandboxes and snapshots.\n  `GET /v1/usage` shows the current figures. An admin can change a user's\n  sandbox limit.\n\n## Conventions\n\n- JSON bodies use camelCase. Times are RFC 3339 in UTC.\n- All endpoints except `/v1/auth/device`, `/v1/auth/device/token`,\n  `/v1/auth/cli/token`, `/v1/auth/refresh` and `/v1/openapi.json` require `Authorization: Bearer \u003caccessToken\u003e`, or the\n  dashboard's `instabox_session` cookie (see `sessionCookie`; mutations then need\n  `X-Instabox-CSRF` and a same-origin `Origin`). Sandbox, snapshot and operation endpoints\n  also accept an API key (see API keys).\n- `/v1/admin/*` and `/metrics` (from outside `metrics.allowFrom`) are\n  admin-only; other users get `403 permission_denied`.\n- Access: GitHub and Google sign-in is public. A verified provider identity\n  creates a regular user. With the server's invite gate on (`inviteRequired`\n  in server.json or `INSTABOX_INVITE_REQUIRED=true`), a new account must then\n  redeem an invite code (`POST /v1/invites/redeem`) before it may use anything\n  but `GET /v1/me` and `POST /v1/auth/logout`: every other route answers\n  `403 invite_required` until it does. `GET /v1/me` says whether the gate is\n  on (`inviteRequired`) and whether the account passed it (`activated`);\n  admins and accounts from before the gate always pass. Disabled users cannot\n  sign in, refresh credentials, call authenticated APIs or connect over SSH.\n  GitHub users keep their positive numeric IDs; Google users get distinct\n  negative IDs assigned by instabox. Matching emails never link accounts.\n- Every non-2xx response has the error envelope\n  `{\"error\": {\"code\", \"message\", \"requestId\", \"retryable\", \"operationId\"}}`.\n- Changes to a sandbox are asynchronous: they return `202 Accepted` with an\n  `Operation`. Poll `GET /v1/operations/{id}?wait=25s` (a long poll) until\n  `state` is `succeeded` or `failed`. Accepted does not mean ready.\n\n## API keys\n\nPrograms, CI and agents use an API key (`ibk_...`) instead of signing in:\nsend `X-API-Key: ibk_...` (or `Authorization: Bearer ibk_...`; when\nboth are sent they must carry the same key, else `400 bad_request`). You\ncreate keys while signed in (`POST /v1/keys`, the dashboard's\nAPI Keys page or `instabox keys create`); the secret is shown once and only its\nsha256 is stored. A key expires after `expiresInDays` (1-365) or, without it, never; a user has at\nmost 20 active keys. An expired or revoked key gets `401 api_key_expired` or\n`401 api_key_revoked`. Disabling a user revokes their keys, and every request\nchecks that the user is not disabled. SSH certificates a key\nissued stop working when the key is revoked or expires.\n\nA key carries scopes: read, write and delete for each resource. A route the\nkey's scopes do not cover returns `403 insufficient_scope`. A `:write` or\n`:delete` scope includes the `:read` scope of the same resource. Signed-in\nsessions have every scope.\n\n- `machines:read`: `GET /v1/machines`, `GET /v1/machines/{id}`, `GET /v1/machines/{id}/connection-info`, `GET /v1/machines/{id}/metrics`, `GET /v1/machines/{id}/usage`, `GET /v1/operations/{id}`, `GET /v1/templates`, `GET /v1/usage`\n- `machines:write`: `POST /v1/machines`, `PATCH /v1/machines/{id}` (timeout, idle pause, metadata), `POST /v1/machines/{id}/start`, `/stop`, `/suspend`, `/resume`, `/fork`, `POST /v1/machines/{id}/connections` (SSH certificates), `POST`, `DELETE /v1/machines/{id}/ssh-tokens` (SSH tokens), `POST /v1/machines/{id}/exec` (run a command)\n- `machines:delete`: `DELETE /v1/machines/{id}`\n- `images:read`: `GET /v1/snapshots`, `GET /v1/snapshots/{id}`, `GET /v1/snapshots/{id}/files`, `GET /v1/templates`, `GET /v1/usage`, `GET /v1/operations/{id}`\n- `images:write`: `POST /v1/machines/{id}/snapshots`\n- `images:delete`: `DELETE /v1/snapshots/{id}`\n\nThe snapshot scopes keep the names they had when snapshots were called\nimages (`images:*`), because existing keys store them.\n\nKeys created before these scopes may still carry `machines:ssh` (SSH\ncertificates and connection info) or `machines:exec` (exec). Those keep\nworking on their routes, but new keys cannot be given them.\n\nAny key may call `GET /v1/me`. Keys never work on\n`/v1/keys` (a leaked key cannot create more keys), `/v1/sessions`,\n`/v1/auth/logout`, `/v1/admin/*` or `/metrics`, and never carry admin\nrights: `start` with `host` gets `403 permission_denied`, another user's\nsnapshot is `404` as for any other user, and resizing gets\n`400 spec_not_allowed`.\n\n## Idempotency\n\nMutating POST and DELETE requests require an `Idempotency-Key` header\n(1-200 printable ASCII characters). Keys are scoped to (user, key):\n- Same key with the same method, path and body hash returns the original\n  response (status and body) without performing the request again.\n- Same key with a different request returns `409 idempotency_conflict`.\n- Records are kept for 7 days; after that the key may be reused.\nGenerate a fresh random key for each action and save it before sending,\nso a retry after a timeout or crash reuses it.\n\n## Sandbox lifecycle\n\n- A sandbox runs at most one operation at a time. A different change\n  while one is running returns `409 operation_conflict` with `operationId`.\n  Repeating the same change returns the running operation.\n- A change that is already in effect (for example `start` on a running\n  sandbox) returns an operation that has already `succeeded`.\n- Pause (`suspend`) keeps the sandbox's running programs, and resume brings\n  them back. `start` on a paused sandbox resumes it. If its programs cannot\n  be brought back, the sandbox boots fresh and `runtime.processesPreserved`\n  is `false`. Files are kept either way.\n- Stop shuts the sandbox down; running programs end, files are kept.\n  `stop` on a paused sandbox returns `409 operation_conflict` (\"start or\n  delete it\").\n- Creating an SSH certificate connection resumes a sandbox paused for\n  idleness; other non-running sandboxes return `409 machine_not_running`.\n- Use requests may resume a sandbox paused for idleness. Manual pause/stop,\n  timeout and archive require an explicit start or resume.\n- Timeouts: a sandbox with `timeoutAt` is paused (`onTimeout: pause`, the\n  default) or deleted (`onTimeout: kill`) shortly after that time, through\n  an ordinary `suspend` or `delete` operation. `timeoutAt` is then\n  cleared, so a later start or resume runs without a timeout unless one is set\n  again (`PATCH /v1/machines/{id}`). A sandbox with an operation in progress\n  is skipped until the operation is done; a `pause` timeout that passes while\n  the sandbox is not running is cleared without an operation and disables\n  automatic wake. An archive deadline reached while idle-paused also\n  requires an explicit resume afterwards.\n- Pause when idle: a sandbox with `autoSuspendAfterSeconds` is paused\n  through an ordinary `suspend` operation (`trigger: idle`) once it has\n  reported no activity for that long (docs/idle-suspend.md); activity is\n  checked about once a minute. Every new sandbox has it, 30 minutes, unless\n  its create or fork request gives another time or 0 (never). Unlike the\n  timeout, the setting stays: a\n  later resume or start keeps it. Every template accepts it: one with the\n  `activity` capability ships probes for its agents; on the others, terminal\n  and CPU activity alone count. Real work (exec, files, terminal traffic and preview requests)\n  and task activity leases also prevent idle suspension. Exec, files,\n  terminal and SSH certificate requests resume sandboxes paused for\n  idleness before proceeding (`trigger: request`). Manual pause/stop,\n  timeout and archive are never automatically undone. Preview links remain\n  offline until resumed. Status reads and keepalives do not record activity.\n- `revision` increases with each accepted change. An operation's results\n  are written only if the sandbox's revision still equals the operation's.\n- Deleted sandboxes are not listed; `GET /v1/machines/{id}` returns\n  `404 not_found` for them.\n\n## Templates, snapshots and forks\n\nA sandbox starts from a template (`templateId`: software that ships with\ninstabox) or from a snapshot (`snapshotId`). A snapshot is a saved copy of\na sandbox's system files and storage; a sandbox created from it gets both.\nA system-files-only snapshot (`rootOnly`, no `dataSizeBytes`) gives the new\nsandbox empty storage. Memory and running programs are not saved. A fork\ncopies a stopped sandbox's system files and storage into a new sandbox.\nBoth need the sandbox stopped and fully saved (`storage.synced`) and a\ntemplate with the `identity-reset` capability. Neither uploads any data,\nso both are quick.\nHOME is `/data/home`, so credentials kept there (gh, Claude, Codex, git,\nSSH) are included in new snapshots and forks. Snapshots are private to\ntheir owner. New snapshot IDs start with `sn_`; older ones with `im_`,\nand stay valid.\n\nWhere the server allows live snapshots, you can also snapshot a running\nsandbox: the request answers `202` with the snapshot in\n`status: pending`. The sandbox's files are frozen for well under a\nsecond while they are copied, then the copy uploads in the background\nand the snapshot becomes `ready`, or `failed` with `error`.\nPoll `GET /v1/snapshots/{id}`. Only a `ready` snapshot can be used. The\ncopy is like the state after a sudden power cut: data that programs had\nnot written to disk yet is not included.\n\n## Deprecated aliases\n\nUntil 2026-10 snapshots were called images. These names keep working\nand are marked `deprecated` here; replies to them carry a `Deprecation`\nheader (RFC 9745) and, for the routes, a `Link` to the successor\n(`rel=\"successor-version\"`):\n\n- `POST /v1/machines/{id}/images`, `GET /v1/images`, `GET /v1/images/{id}`,\n  `GET /v1/images/{id}/files`, `DELETE /v1/images/{id}`: the\n  `/snapshots` routes. They reply in the old shape: `GET /v1/images`\n  returns `{\"images\": [...]}`, and each snapshot also has\n  `official: false`, `owned: true` and `preinstalled: []`.\n- `imageId` in `POST /v1/machines`: `snapshotId` (both: they must be equal).\n- `Machine.image`, `Usage.images` and `UsageLimits.maxImages`: sent with\n  the same values as `snapshot`, `snapshots` and `maxSnapshots`.\n\nOfficial images were removed: software ships in templates.\n\n## SSH\n\nConnect with `ssh -p \u003cport\u003e \u003cmachineId\u003e@\u003chost\u003e`. Clients\nauthenticate with a user certificate from\n`POST /v1/machines/{id}/connections` (principal = sandbox ID) and trust the\nserver with a known_hosts line\n`@cert-authority [\u003chost\u003e]:\u003cport\u003e \u003chostTrust.publicKey\u003e`.\n\nWithout a key or the instabox CLI, a plain ssh client logs in with an SSH\ntoken from `POST /v1/machines/{id}/ssh-tokens` as its user name:\n`ssh ibssh_…@\u003chost\u003e`, with `-p \u003cport\u003e` unless SSH is on port 22\n(the reply's `command`). The token is accepted with the `none` method,\nwhich OpenSSH tries first, and ssh asks once to confirm the server's host\nkey (`hostKeyFingerprint`). A token is a secret: it works for anyone who\nhas it until it expires, is revoked (`DELETE /v1/machines/{id}/ssh-tokens`)\nor the sandbox is deleted. Supported:\nsessions (pty, shell, exec, subsystem, env, signals) and local port\nforwarding. Not supported in v1: agent forwarding, X11, remote forwarding.\n\nmosh works through the same login: when a session runs `mosh-server new`,\nthe server opens a UDP port that relays to the sandbox's mosh-server and\nputs it in the `MOSH CONNECT` line. The relay outlives the SSH session and\ncloses after a day without traffic, when the sandbox stops or is deleted,\nor when the grant is revoked.\nConnections are closed when the sandbox stops, pauses or is deleted.\n\n## Exec\n\n`POST /v1/machines/{id}/exec` runs one command without an SSH client: the\nserver logs in to the sandbox the way SSH does (user `dev`, HOME\n`/data/home`), runs `bash -lc \u003ccmd\u003e` and returns its output and exit code\nin the response. Output is collected, not streamed; use SSH for long or\ninteractive work.\n","summary":"Create and manage cloud sandboxes for AI coding agents.","title":"instabox API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/auth/cli/start":{"get":{"description":"RFC 8252 loopback flow for CLIs and desktop apps. The client listens on\na loopback port, opens this URL in the browser and waits for one request\nto its redirect URI. Invalid parameters get an HTML error page (no\nredirect). Without `provider`, redirects to the dashboard's\n`/dashboard/cli-authorize?request=...` with a signed request valid for\n10 minutes. The browser confirms its existing Instabox account, or\nsigns in with GitHub or Google before confirming. Approval is an\nexplicit, CSRF-protected POST to `/v1/auth/cli/authorize`.\nAn explicit `provider` starts that provider's OAuth flow directly.\nOn completion the browser returns to\n`redirect_uri?code=\u003cibc_...\u003e\u0026state=\u003cstate\u003e`, or on failure to\n`redirect_uri?error=\u003cerror\u003e\u0026error_description=...\u0026state=\u003cstate\u003e` where\n`error` is `access_denied` (cancelled or disabled), `server_error`, or, with\nthe invite gate on, `invite_required` (the account is not activated and no\n`invite` was sent) or `invite_invalid` (the code cannot be redeemed; checked\nbefore the provider opens, by the identity service's invites plugin when\nBetter Auth is configured), or `quota_exceeded` (too many invalid invite codes\nfrom this client address in the past hour, as for `POST /v1/invites/redeem`;\nthe code is not checked). A redeemable `invite` is redeemed as the sign-in\ncompletes. The client must check `state`, then call `POST /v1/auth/cli/token`\nwithin 60 seconds.\n","operationId":"cliLogin","parameters":[{"description":"An invite code (`482913`, or an older `ibi_...` code) for an account that is not activated yet (`instabox login --invite-code`).","in":"query","name":"invite","schema":{"maxLength":64,"type":"string"}},{"description":"Optional explicit provider; omitted means confirm an account in the dashboard.","in":"query","name":"provider","schema":{"enum":["github","google"],"type":"string"}},{"description":"Exactly `http://127.0.0.1:\u003cport\u003e/callback` or `http://[::1]:\u003cport\u003e/callback`, port 1024-65535 (no `localhost`, query or fragment).","in":"query","name":"redirect_uri","required":true,"schema":{"type":"string"}},{"description":"Client CSRF value, 16-256 printable ASCII characters (no space), echoed to the redirect URI.","in":"query","name":"state","required":true,"schema":{"maxLength":256,"minLength":16,"type":"string"}},{"description":"PKCE challenge, base64url(sha256(verifier)) without padding (43-128 characters).","in":"query","name":"code_challenge","required":true,"schema":{"maxLength":128,"minLength":43,"pattern":"^[A-Za-z0-9_-]+$","type":"string"}},{"in":"query","name":"code_challenge_method","required":true,"schema":{"enum":["S256"],"type":"string"}}],"responses":{"302":{"description":"Redirect to dashboard account confirmation or the explicitly selected provider."},"400":{"description":"Invalid parameters (HTML page)."},"503":{"description":"Browser sign-in is not configured (`github.clientSecretFile`)."}},"security":[],"summary":"Native client (loopback) sign-in; open in the user's browser.","tags":["auth"]}},"/auth/github/callback":{"get":{"operationId":"webCallback","parameters":[{"in":"query","name":"code","schema":{"type":"string"}},{"in":"query","name":"state","schema":{"type":"string"}}],"responses":{"302":{"description":"Signed in, or redirected through Better Auth completion."},"400":{"description":"State mismatch","expired sign-in or GitHub error (HTML page).":null},"403":{"description":"The user was disabled by an admin (HTML page)."}},"security":[],"summary":"GitHub web flow callback; sets the `instabox_session` cookie and redirects to `/`, or for a native client sign-in redirects to its loopback URI.","tags":["auth"]}},"/auth/github/login":{"get":{"operationId":"webLogin","parameters":[{"description":"Optional signed request from `/auth/cli/start`; after sign-in return to dashboard CLI account confirmation.","in":"query","name":"cli_request","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to GitHub."},"400":{"description":"Invalid or expired CLI return request."},"503":{"description":"Browser sign-in is not configured (`github.clientSecretFile`)."}},"security":[],"summary":"Browser sign-in; redirects to GitHub (web flow with state and PKCE S256).","tags":["auth"]}},"/auth/google/callback":{"get":{"operationId":"googleCallback","responses":{"302":{"description":"Redirect to sign-in completion."}},"security":[],"summary":"Google OAuth callback.","tags":["auth"]}},"/auth/google/complete":{"get":{"operationId":"googleComplete","responses":{"302":{"description":"Signed in."},"400":{"description":"Invalid state or cancelled sign-in."},"403":{"description":"Disabled user."},"503":{"description":"Identity service unavailable or identity rejected."}},"security":[],"summary":"Exchange the verified identity for an Instabox session or CLI code.","tags":["auth"]}},"/auth/google/login":{"get":{"operationId":"googleLogin","parameters":[{"description":"Optional signed request from `/auth/cli/start`; after sign-in return to dashboard CLI account confirmation.","in":"query","name":"cli_request","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to Google."},"400":{"description":"Invalid or expired CLI return request."},"503":{"description":"Google sign-in is not configured."}},"security":[],"summary":"Start Google sign-in through Better Auth.","tags":["auth"]}},"/auth/providers":{"get":{"operationId":"authProviders","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"providers":{"items":{"enum":["github","google"],"type":"string"},"type":"array"}},"type":"object"}}},"description":"Enabled providers."}},"security":[],"summary":"List configured sign-in providers.","tags":["auth"]}},"/metrics":{"get":{"description":"Admin only from outside `metrics.allowFrom` (default loopback), where it needs an admin token or session; from inside the allowlist no credentials are needed.","operationId":"metrics","responses":{"200":{"content":{"text/plain":{"schema":{"type":"string"}}},"description":"Metrics."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Prometheus metrics (text format 0.0.4; admin only).","tags":["admin"]}},"/v1/admin/connections":{"get":{"description":"Admin only.","operationId":"adminListConnections","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminConnectionList"}}},"description":"Connections, oldest first."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Live SSH gateway connections (admin only).","tags":["admin"]}},"/v1/admin/connections/{id}":{"delete":{"description":"Admin only. Not a revocation; the client may connect again with its certificate. No Idempotency-Key.","operationId":"adminCloseConnection","parameters":[{"description":"Gateway connection ID (`gc_\u003cn\u003e`) from the list.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Closed."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Close one live gateway connection (admin only).","tags":["admin"]}},"/v1/admin/hosts":{"get":{"description":"Admin only.","operationId":"adminListHosts","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminHostList"}}},"description":"Hosts by name."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Hosts with their last report and the sandboxes on them (admin only).","tags":["admin"]}},"/v1/admin/hosts/{name}":{"delete":{"description":"Admin only. Removes the host's enrollment and stops polling and scheduling it. Its sandboxes stay assigned to it; drain the host first to move them. The node can enroll again with any node token. Hosts configured in server.json must be removed there; they return 409 `operation_conflict`, as do hosts that were not enrolled. No Idempotency-Key is required.","operationId":"adminRemoveHost","parameters":[{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"The enrollment was removed."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"summary":"Remove an enrolled host (admin only).","tags":["admin"]},"patch":{"description":"Admin only. Disabled hosts get no new sandboxes; sandboxes already there keep running. The setting persists across restarts. `disabled` in server.json still wins. Enabling a drained host (or one whose drain failed) forgets the drain; a host being drained cannot be enabled (409 `operation_conflict`). Idempotent; no Idempotency-Key.","operationId":"adminUpdateHost","parameters":[{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHostRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminHost"}}},"description":"The host."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"summary":"Enable or disable scheduling on a host (admin only).","tags":["admin"]}},"/v1/admin/hosts/{name}/drain":{"get":{"description":"Admin only. `idle` when no drain was started (or it was forgotten by enabling the host).","operationId":"adminHostDrainStatus","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostDrain"}}},"description":"The drain."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The progress of a host's drain (admin only).","tags":["admin"]},"parameters":[{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"post":{"description":"Admin only. Turns the host's scheduling off and moves its sandboxes, in the background, with ordinary operations (marked `\"trigger\": \"drain\"` in their request): a running sandbox is paused (its memory state committed to object storage) and resumed on another host that can resume it, keeping its processes; a stopped or paused one has its newest state committed (if it is only local), is unassigned from the host (its next start places it elsewhere) and its local copy is evicted; local copies of sandboxes that live elsewhere are evicted. Drain operations do not count against the owner's quotas; host capacity still applies. A sandbox with an operation in flight is moved once it ends. Retryable failures are retried with backoff; a sandbox the drain gives up on is listed in `failed` (one it paused runs again where it was) and the drain ends `failed`; `drained` once no sandbox and no local copy is left on the host. The drain survives server restarts. At most `limits.drainConcurrency` (default 4) of its operations run at once. Idempotent, no Idempotency-Key: a drain in progress or drained is left as it is; a failed one is started again for the sandboxes that are left. Before starting, the other schedulable hosts of the host's CPU class must be able to hold the sandboxes that run there (the scheduler's capacity model); otherwise 409 `no_capacity` says what is short, and nothing changes.","operationId":"adminDrainHost","responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostDrain"}}},"description":"Draining (or already drained); the drain's progress."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`no_capacity`: the other hosts cannot hold the sandboxes that run on the host (the message names the dimension and how much is short); `operation_conflict`: object storage is not configured, so nothing can move."}},"summary":"Move every sandbox off a host before removing it (admin only).","tags":["admin"]}},"/v1/admin/invites":{"get":{"description":"Admin only. Newest first. The codes themselves are included, to hand out.","operationId":"adminListInvites","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteList"}}},"description":"Invite codes."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Every invite code with its state and who redeemed it (admin only).","tags":["admin"]},"post":{"description":"Admin only. Makes `count` codes (default 1, at most 100), each good for `maxUses` accounts (default 1, at most 1000), of `role` `user` (default: activates the account) or `admin` (also grants the administrator role), expiring at `expiresAt` (absent: never; at most two years out), with `note` on each. Each code is written to the audit log (`invite.create`), as revoking, deleting and redeeming are. The reply holds the codes. No Idempotency-Key. Browser sessions need Origin and X-Instabox-CSRF.","operationId":"adminCreateInvites","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvitesRequest"}}},"required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedInvites"}}},"description":"The new codes."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Create invite codes (admin only).","tags":["admin"]}},"/v1/admin/invites/{id}":{"delete":{"description":"Admin only. A redeemed code is kept so the record of who it activated stays: revoke it instead (`409 operation_conflict`). No Idempotency-Key.","operationId":"adminDeleteInvite","parameters":[{"in":"path","name":"id","required":true,"schema":{"pattern":"^inv_[a-z2-7]{26}$","type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`operation_conflict`: the code was redeemed; revoke it."}},"summary":"Delete an invite code nobody redeemed (admin only).","tags":["admin"]}},"/v1/admin/invites/{id}/revoke":{"post":{"description":"Admin only. The code cannot be redeemed any more; accounts it activated stay activated (disable them separately). Repeatable: a second revoke keeps the first time. No Idempotency-Key. Browser sessions need Origin and X-Instabox-CSRF.","operationId":"adminRevokeInvite","parameters":[{"in":"path","name":"id","required":true,"schema":{"pattern":"^inv_[a-z2-7]{26}$","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invite"}}},"description":"The revoked code."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Revoke an invite code (admin only).","tags":["admin"]}},"/v1/admin/machines":{"get":{"description":"Admin only. Filters, sorting and paging run in the database. Without `limit` every match is returned. `total` is the number of matches; `hosts` and `templates` are the filter choices.","operationId":"adminListMachines","parameters":[{"description":"Case-insensitive substring of the name, ID, owner login or owner name.","in":"query","name":"q","schema":{"type":"string"}},{"description":"Comma-separated sandbox states (any of them).","in":"query","name":"state","schema":{"type":"string"}},{"description":"Host name; `-` selects sandboxes not placed on a host.","in":"query","name":"host","schema":{"type":"string"}},{"description":"Template ID.","in":"query","name":"template","schema":{"type":"string"}},{"description":"Owner user ID or login (exact, case-insensitive).","in":"query","name":"owner","schema":{"type":"string"}},{"description":"Sandboxes without a value (never accessed, no timeout, not placed) sort last.","in":"query","name":"sort","schema":{"default":"created","enum":["created","name","owner","state","stateSince","host","template","lastAccess","timeout","snapshots","publicPorts"],"type":"string"}},{"in":"query","name":"order","schema":{"default":"asc","enum":["asc","desc"],"type":"string"}},{"description":"Page size (absent - every match).","in":"query","name":"limit","schema":{"maximum":500,"minimum":1,"type":"integer"}},{"in":"query","name":"offset","schema":{"default":0,"minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminMachineList"}}},"description":"Sandboxes."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Live sandboxes of every user, filtered, sorted and paged (admin only).","tags":["admin"]}},"/v1/admin/machines/{id}":{"get":{"description":"Admin only. Deleted sandboxes are returned too.","operationId":"adminGetMachine","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminMachineDetail"}}},"description":"The sandbox."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One sandbox with its recent operations and public ports (admin only).","tags":["admin"]}},"/v1/admin/machines/{id}/move":{"post":{"description":"Admin only. The sandbox's uploaded snapshot is copied to the target pool's object store (only what it lacks) and the sandbox switches pools; its next start or resume restores it there (docs/hybrid-pools.md). 409 while it runs, has an operation in progress or has local changes not uploaded yet.","operationId":"adminMoveMachinePool","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MovePoolRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MovePoolResult"}}},"description":"Moved."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"summary":"Move a stopped or suspended sandbox to another host pool (admin only).","tags":["admin"]}},"/v1/admin/metrics/query":{"get":{"description":"Admin only. Forwards one PromQL/MetricsQL instant query to the metrics store (VictoriaMetrics on the control host, server.json `metrics.storeUrl`, default http://127.0.0.1:8428/vm), which scrapes this server's /metrics every 30 s, and returns its answer unchanged in the Prometheus HTTP API format (`{\"status\":\"success\",\"data\":{\"resultType\":\"vector\",\"result\":[...]}}`). The query is at most 4096 bytes, runs for at most 20 s and its answer is at most 8 MiB. `metrics_unavailable` (503): the store is turned off, unreachable or failing.","operationId":"adminMetricsQuery","parameters":[{"description":"PromQL/MetricsQL expression.","in":"query","name":"query","required":true,"schema":{"maxLength":4096,"type":"string"}},{"description":"Evaluation time: Unix seconds or RFC 3339 (default: now).","in":"query","name":"time","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricsQueryResult"}}},"description":"The store's answer (Prometheus HTTP API)."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"503":{"$ref":"#/components/responses/MetricsUnavailable"}},"summary":"One instant query against the metrics store (admin only, read-only).","tags":["admin"]}},"/v1/admin/metrics/query_range":{"get":{"description":"Admin only. Like GET /v1/admin/metrics/query over a time range: `end` after `start`, at most 92 days apart; `step` whole seconds (at least 1 s, default the range over 300 points and at least 15 s) giving at most 1500 points per series.","operationId":"adminMetricsQueryRange","parameters":[{"description":"PromQL/MetricsQL expression.","in":"query","name":"query","required":true,"schema":{"maxLength":4096,"type":"string"}},{"description":"Unix seconds or RFC 3339.","in":"query","name":"start","required":true,"schema":{"type":"string"}},{"description":"Unix seconds or RFC 3339.","in":"query","name":"end","required":true,"schema":{"type":"string"}},{"description":"Seconds (60) or a duration (1m).","in":"query","name":"step","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricsQueryResult"}}},"description":"The store's answer (Prometheus HTTP API, resultType matrix)."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"503":{"$ref":"#/components/responses/MetricsUnavailable"}},"summary":"One range query against the metrics store (admin only, read-only).","tags":["admin"]}},"/v1/admin/operations":{"get":{"description":"Admin only.","operationId":"adminListOperations","parameters":[{"description":"`queued`, `running`, `succeeded`, `failed`, `active` (queued or running) or `stuck` (active for longer than stuckMinutes). Absent: all.","in":"query","name":"state","schema":{"enum":["queued","running","succeeded","failed","active","stuck"],"type":"string"}},{"in":"query","name":"limit","schema":{"default":100,"maximum":500,"minimum":1,"type":"integer"}},{"in":"query","name":"stuckMinutes","schema":{"default":15,"maximum":10080,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminOperationList"}}},"description":"Operations."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Operations of every user, most recently updated first (admin only).","tags":["admin"]}},"/v1/admin/snapshots/{id}/copy":{"post":{"description":"Admin only.","operationId":"adminCopySnapshotPool","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MovePoolRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MovePoolResult"}}},"description":"The copy."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"summary":"Copy a snapshot to another host pool as a new snapshot of the same owner (admin only).","tags":["admin"]}},"/v1/admin/snapshots/{id}/move":{"post":{"description":"Admin only. 409 while a sandbox created from it has no snapshot of its own yet.","operationId":"adminMoveSnapshotPool","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MovePoolRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MovePoolResult"}}},"description":"Moved."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"summary":"Move a snapshot to another host pool (admin only).","tags":["admin"]}},"/v1/admin/storage":{"get":{"description":"Admin only.","operationId":"adminStorage","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminStorage"}}},"description":"Storage overview."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Object storage configuration, last GC and usage runs, totals (admin only).","tags":["admin"]}},"/v1/admin/users":{"get":{"description":"Admin only. Deleted users are not listed. Search and pagination run on the server, ordered by login\nthen user ID (waitlist: signup time then ID). Without limit, returns all matches for existing clients.\n","operationId":"adminListUsers","parameters":[{"description":"Case-insensitive literal substring of login, name, email or user ID. Leading and trailing whitespace is ignored.","in":"query","name":"q","schema":{"type":"string"}},{"description":"Only enabled, unactivated members awaiting an invite, oldest signups first. Excludes configured and database admins.","in":"query","name":"waitlist","schema":{"default":false,"type":"boolean"}},{"description":"Maximum users to return. Omit to return all matches.","in":"query","name":"limit","schema":{"maximum":500,"minimum":1,"type":"integer"}},{"description":"Number of matching users to skip.","in":"query","name":"offset","schema":{"default":0,"maximum":1073741824,"minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserList"}}},"description":"Users by login."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Users who signed in, with counts, configured allowlist and effective admin roles (admin only).","tags":["admin"]}},"/v1/admin/users/export.csv":{"get":{"description":"Admin only. All matches of the admin Users view, ordered as adminListUsers. Columns: login, name, email,\nemail_source (recorded, login, github-verified or empty), provider, role, status (waitlist, active,\ndisabled or admin), signed_up, last_sign_in, machines. Fields starting with =, +, - or @ get a leading '.\n","operationId":"adminExportUsers","parameters":[{"description":"waitlist matches adminListUsers' waitlist=true.","in":"query","name":"view","schema":{"default":"all","enum":["all","waitlist"],"type":"string"}},{"description":"As adminListUsers' q.","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"text/csv":{"schema":{"type":"string"}}},"description":"The CSV, with a header row."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Users as CSV, with emails (admin only).","tags":["admin"]}},"/v1/admin/users/{id}":{"delete":{"description":"Admin only. In one transaction: marks the account deleted (the row stays as a tombstone, so history keeps its owner) and disabled, revokes every session, API key and SSH grant of the user, removes their Vault items, deletes their snapshots and queues a delete operation for each sandbox, as their own deletes would. Sandboxes busy with another operation are deleted when it ends. Deleted machines, operations, usage and the audit log are kept; `user.delete` is written to the audit log. The user leaves `GET /v1/admin/users`. Signing in again with the same identity starts a new account (not an admin, no limit overrides, not activated: with the invite gate on it must redeem a code). An admin cannot delete their own account or another admin's. Deleting a deleted user is 404. No Idempotency-Key. Browser sessions need Origin and X-Instabox-CSRF.","operationId":"adminDeleteUser","parameters":[{"description":"Instabox user ID.","in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedUser"}}},"description":"Deleted; machine and snapshot deletions started."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`operation_conflict`: the user is you, or an admin."}},"summary":"Delete a user and everything they own (admin only).","tags":["admin"]},"patch":{"description":"Admin only. Overrides the server.json limit maxMachinesPerUser (default 5) for one user (admins are not exempt from quotas). This is the only per-user sandbox quota; there are no vCPU or memory quotas. Sandboxes over a lowered limit stay; the next create or fork is refused. Idempotent; no Idempotency-Key. Browser sessions need Origin and X-Instabox-CSRF.","operationId":"adminUpdateUser","parameters":[{"description":"Numeric GitHub user ID.","in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}},"description":"The user."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Set or clear a user's sandbox limit (admin only).","tags":["admin"]}},"/v1/admin/users/{id}/disable":{"post":{"description":"Admin only. Sets `disabledAt` and revokes every session of the user (CLI and browser); the SSH gateway refuses their connections. Their sandboxes are kept. Signing in again is refused until an administrator enables the account. Repeatable; no Idempotency-Key. Browser sessions need `X-Instabox-CSRF`.","operationId":"adminDisableUser","parameters":[{"description":"Instabox user ID.","in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}},"description":"The user."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`operation_conflict`: the user is on the allowlist."}},"summary":"Disable a user account (admin only).","tags":["admin"]}},"/v1/admin/users/{id}/enable":{"post":{"description":"Admin only. Clears `disabledAt`; the user can sign in again. Repeatable; no Idempotency-Key.","operationId":"adminEnableUser","parameters":[{"description":"Instabox user ID.","in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}},"description":"The user."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Restore a disabled user's access (admin only).","tags":["admin"]}},"/v1/admin/users/{id}/promote":{"post":{"description":"Admin only. Persists the role in the database and takes effect on the next request, including existing browser and CLI sessions. Grants all administrator permissions. API keys cannot use admin routes. Repeatable; no Idempotency-Key. Browser sessions need Origin and X-Instabox-CSRF.","operationId":"adminPromoteUser","parameters":[{"description":"Instabox user ID.","in":"path","name":"id","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"204":{"description":"Administrator role granted."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"The user does not have active access."}},"summary":"Grant an active user the administrator role (admin only).","tags":["admin"]}},"/v1/auth/cli/authorize":{"post":{"description":"Requires the browser session cookie, its `X-Instabox-CSRF` token and a\ntrusted Origin. Bearer tokens and API keys cannot authorize a CLI.\n`userId` must match the current session, preventing authorization as a\ndifferent account if the browser account changed after rendering.\nApproval issues a one-use, PKCE-bound code valid for 60 seconds. Denial\nreturns `access_denied`. The browser navigates to `redirectUrl`, which\ncontains the validated loopback callback and the original client state.\n","operationId":"authorizeCLI","requestBody":{"content":{"application/json":{"schema":{"properties":{"decision":{"enum":["approve","deny"],"type":"string"},"request":{"type":"string"},"userId":{"format":"int64","type":"integer"}},"required":["request","userId","decision"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"redirectUrl":{"type":"string"}},"required":["redirectUrl"],"type":"object"}}},"description":"Navigate the browser to the CLI callback."},"400":{"description":"Invalid","tampered or expired request or invalid decision.":null},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"409":{"description":"The signed-in account changed; reload the confirmation page."},"500":{"$ref":"#/components/responses/Internal"}},"security":[{"sessionCookie":[]}],"summary":"Approve or deny CLI access as the displayed browser account.","tags":["auth"]}},"/v1/auth/cli/request":{"get":{"description":"Requires a sign-in session. Returns the validated loopback destination and expiry without issuing a code. Requests expire after 10 minutes.","operationId":"getCLIAuthorizationRequest","parameters":[{"description":"Signed request from the dashboard redirect returned by `/auth/cli/start`.","in":"query","name":"request","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"expiresAt":{"format":"date-time","type":"string"},"redirectUri":{"type":"string"}},"required":["redirectUri","expiresAt"],"type":"object"}}},"description":"Pending request."},"400":{"description":"Invalid","tampered or expired request.":null},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"Validate a pending CLI sign-in request for account confirmation.","tags":["auth"]}},"/v1/auth/cli/token":{"post":{"description":"Second half of the loopback sign-in (RFC 8252, see `GET /auth/cli/start`).\n`code` is the one-time code delivered to the client's redirect URI\n(valid 60 seconds, single use), `codeVerifier` the PKCE verifier\n(43-128 characters `[A-Za-z0-9-._~]`) whose S256 hash was sent as\n`code_challenge`, and `redirectUri` exactly the `redirect_uri` sent to\n`/auth/cli/start`. Issues tokens like the device flow. Any mismatch,\nexpiry or reuse returns `400 invalid_grant` and burns the code;\npresenting an already exchanged code also revokes the tokens issued\nfrom it.\n","operationId":"exchangeCLICode","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CLITokenRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tokens"}}},"description":"Login complete."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`invalid_grant` (start the sign-in again) or `bad_request`."},"403":{"$ref":"#/components/responses/PermissionDenied"},"500":{"$ref":"#/components/responses/Internal"}},"security":[],"summary":"Exchange a native client sign-in code for tokens.","tags":["auth"]}},"/v1/auth/device":{"post":{"description":"The body is optional. New GitHub users are registered automatically.\nWith the invite gate on (docs/invites.md), an account that is not\nactivated yet must send `invite`: the code is kept with the flow and\nredeemed by the poll that completes the sign-in (`instabox login\n--invite-code`).\n","operationId":"startDeviceLogin","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceStartRequest"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceStart"}}},"description":"Show `verificationUri` and `userCode` to the user, then poll the token endpoint every `interval` seconds."},"429":{"$ref":"#/components/responses/QuotaExceeded"},"500":{"$ref":"#/components/responses/Internal"}},"security":[],"summary":"Start a device login (GitHub device flow through instabox).","tags":["auth"]}},"/v1/auth/device/token":{"post":{"description":"Returns `400` with code `authorization_pending` until the user approves,\n`slow_down` if polled too fast (add 5 seconds to the interval),\n`expired_token` once the device code expired and `access_denied` if the\nuser declined. New users are registered automatically. A disabled user gets\n`403 access_denied`. With the invite gate on, an account that is not activated\nyet gets `403 invite_required` when the flow started without an invite code and\n`403 invite_invalid` when the code is unknown, expired, revoked or used up (the\naccount exists afterwards, unactivated; the dashboard can still activate it).\nToo many invalid invite codes from this address or account in the past hour\n(see `POST /v1/invites/redeem`) is `429 quota_exceeded` (retryable later; the\ncode is not looked at). After a 403 or 429 the device code is spent: start again.\n","operationId":"pollDeviceToken","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tokens"}}},"description":"Login complete."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`authorization_pending`, `slow_down`, `expired_token`, `access_denied` or `bad_request`."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`access_denied` (disabled), `invite_required` or `invite_invalid` (the invite gate)."},"429":{"$ref":"#/components/responses/QuotaExceeded"},"500":{"$ref":"#/components/responses/Internal"}},"security":[],"summary":"Poll for the tokens of a device login.","tags":["auth"]}},"/v1/auth/logout":{"post":{"description":"With the session cookie, revokes the browser session and clears the cookie.","operationId":"logout","responses":{"204":{"description":"Revoked."},"401":{"$ref":"#/components/responses/Unauthenticated"},"500":{"$ref":"#/components/responses/Internal"}},"summary":"Revoke the presented access token and its refresh token.","tags":["auth"]}},"/v1/auth/refresh":{"post":{"description":"The refresh token rotates; the old one stops working. Store the new pair.","operationId":"refreshTokens","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tokens"}}},"description":"New tokens."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"500":{"$ref":"#/components/responses/Internal"}},"security":[],"summary":"Exchange a refresh token for new tokens.","tags":["auth"]}},"/v1/images":{"get":{"deprecated":true,"description":"The old name of GET /v1/snapshots, in the old shape. API keys: needs one of the scopes `images:read`.","operationId":"listImages","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeprecatedImageList"}}},"description":"Your snapshots, oldest first.","headers":{"Deprecation":{"$ref":"#/components/headers/Deprecation"}}},"401":{"$ref":"#/components/responses/Unauthenticated"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Deprecated: GET /v1/snapshots.","tags":["snapshots"]}},"/v1/images/{id}":{"delete":{"deprecated":true,"description":"The old name of DELETE /v1/snapshots/{id}. API keys: needs one of the scopes `images:delete`.","operationId":"deleteImage","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Deleted.","headers":{"Deprecation":{"$ref":"#/components/headers/Deprecation"}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Deprecated: DELETE /v1/snapshots/{id}.","tags":["snapshots"]},"get":{"deprecated":true,"description":"The old name of GET /v1/snapshots/{id}, in the old shape. API keys: needs one of the scopes `images:read`.","operationId":"getImage","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeprecatedImage"}}},"description":"The snapshot.","headers":{"Deprecation":{"$ref":"#/components/headers/Deprecation"}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Deprecated: GET /v1/snapshots/{id}.","tags":["snapshots"]},"parameters":[{"$ref":"#/components/parameters/SnapshotID"}]},"/v1/images/{id}/files":{"get":{"deprecated":true,"description":"The old name of GET /v1/snapshots/{id}/files. API keys: needs one of the scopes `images:read`.","operationId":"getImageFiles","parameters":[{"$ref":"#/components/parameters/FilesPath"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Files"}}},"description":"The file or directory.","headers":{"Deprecation":{"$ref":"#/components/headers/Deprecation"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Deprecated: GET /v1/snapshots/{id}/files.","tags":["snapshots"]},"parameters":[{"$ref":"#/components/parameters/SnapshotID"}]},"/v1/invites/mine":{"get":{"description":"An activated account has one user code of its own (docs/invites.md, \"Users' own codes\"), made on its first call: it lets three people in, never expires and is never replenished once used up or revoked. The account cannot make more, pick the role or revoke it; admins see and revoke it on the Invite codes page (an admin deleting it while nobody redeemed it lets the next call make a fresh one). An account that is not activated gets `403 invite_required`. Sign-in sessions only.","operationId":"myInvite","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyInvite"}}},"description":"The account's own code, its uses and who joined with it."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"summary":"The signed-in account's own invite code to hand out.","tags":["auth"]}},"/v1/invites/redeem":{"post":{"description":"Open to accounts that have not passed the invite gate yet (like `GET /v1/me` and logout). Codes are 6 digits (`482913`); codes from before 2026-10-13 are `ibi_` + 20 base32 characters and keep working. The code is matched after trimming, dropping spaces and dashes inside a numeric code (`482 913`) and lowercasing an `ibi_` code. A user code activates the account for good; an admin code activates it and grants the administrator role, also to an account that is activated already. An account the code can do nothing for (activated, for a user code; an admin, for an admin code) gets its state back and consumes nothing. After 10 invalid codes in an hour from one account or one client address (an IPv6 address counts by its /64; invalid codes at a sign-in's start count too), every code is refused with `429 quota_exceeded` (retryable, saying nothing about the code) until the hour passes. With Better Auth configured the redemption runs through the identity service's invites plugin (`POST /invite/redeem`); either way it is `redeem_invite_code()` in the database. Idempotent by nature; no Idempotency-Key. Browser sessions need Origin and X-Instabox-CSRF.","operationId":"redeemInvite","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedeemInviteRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Activation"}}},"description":"The account is activated."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"description":"`bad_request`: no code; `invite_invalid`: the code is unknown, expired, revoked or used up."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"429":{"$ref":"#/components/responses/QuotaExceeded"}},"summary":"Activate the signed-in account with an invite code.","tags":["auth"]}},"/v1/keys":{"get":{"operationId":"listAPIKeys","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyList"}}},"description":"Keys, newest first. Secrets are never returned."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"summary":"List your API keys (expired ones included, revoked ones not). Signed-in sessions only.","tags":["keys"]},"post":{"description":"The reply holds the secret (`key`), shown only here. No Idempotency-Key is used:\nthe secret is never stored, so a reply cannot be replayed.\nAt most 20 active keys per user (`429 quota_exceeded`).\n","operationId":"createAPIKey","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAPIKeyRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedAPIKey"}}},"description":"Created."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"429":{"$ref":"#/components/responses/QuotaExceeded"}},"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"summary":"Create an API key. Signed-in sessions only; an API key gets 403 permission_denied.","tags":["keys"]}},"/v1/keys/{id}":{"delete":{"description":"Requests with the key then fail with `401 api_key_revoked`; SSH certificates the key issued are revoked too (they stop working and their open sessions close). Repeating it returns 404.","operationId":"revokeAPIKey","parameters":[{"in":"path","name":"id","required":true,"schema":{"pattern":"^key_[a-z2-7]{26}$","type":"string"}}],"responses":{"204":{"description":"Revoked."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"summary":"Revoke one of your API keys. Signed-in sessions only.","tags":["keys"]}},"/v1/machines":{"get":{"description":"API keys: needs one of the scopes `machines:read`.","operationId":"listMachines","parameters":[{"description":"Opaque `nextCursor` from a previous page.","in":"query","name":"cursor","required":false,"schema":{"type":"string"}},{"description":"Page size.","in":"query","name":"limit","required":false,"schema":{"minimum":1,"type":"integer"}},{"description":"`key:value` (split at the first `:`); only sandboxes whose metadata has the key with exactly that value. Repeatable; all terms must match (AND).","explode":true,"in":"query","name":"metadata","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MachineList"}}},"description":"A page of sandboxes. `nextCursor` is null on the last page."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"List your sandboxes.","tags":["machines"]},"post":{"description":"Returns a `create` operation whose `machineId` is the new sandbox. Start from a template (`templateId`) or one of your snapshots (`snapshotId`). Every new sandbox gets the fixed size (4 vCPUs, 8 GB memory); leave the size fields out, or send exactly that size. Any other size is refused with `400 spec_not_allowed`. Counts toward your limit of 5 sandboxes (`429 quota_exceeded`). When no host has room, the operation fails with `no_capacity` (retryable). A create from a template or from a system-files-only snapshot that fails before it gets a host (`no_capacity`, or any other error at that point) has its sandbox deleted with the failure (GET returns 404), so it does not count toward your limit and its name is free to retry. A create from a snapshot with storage stays, `stopped`; start it later. API keys: needs one of the scopes `machines:write`.","operationId":"createMachine","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMachineRequest"}}},"required":true},"responses":{"202":{"$ref":"#/components/responses/OperationAccepted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Create a sandbox.","tags":["machines"]}},"/v1/machines/{id}":{"delete":{"description":"Snapshots saved from it are kept. API keys: needs one of the scopes `machines:delete`.","operationId":"deleteMachine","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"202":{"$ref":"#/components/responses/OperationAccepted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Delete a sandbox, its system files and its storage.","tags":["machines"]},"get":{"description":"API keys: needs one of the scopes `machines:read`.","operationId":"getMachine","parameters":[{"$ref":"#/components/parameters/MachineRef"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Machine"}}},"description":"The sandbox."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Get a sandbox.","tags":["machines"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}],"patch":{"description":"Synchronous. Absent fields are unchanged. `timeoutSeconds`, `onTimeout`, `autoSuspendAfterSeconds` and `metadata` apply in any state, also while an operation runs, and do not change `revision`: `timeoutSeconds` sets `timeoutAt` to now plus that (0 clears it), `metadata` replaces the metadata (`{}` clears it).\nResizing (`rootDiskGiB`, `persistentDiskGiB`) is admin-only, from a signed-in session: sandbox sizes are fixed, so anyone else gets `400 spec_not_allowed`. Sizes only grow (a smaller size is `400`); growing needs the sandbox stopped with no operation in progress and a template with the `root-grow` capability, and takes effect the next time the sandbox boots. API keys: needs one of the scopes `machines:write`.","operationId":"updateMachine","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMachineRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Machine"}}},"description":"The sandbox, updated."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Set a sandbox's timeout, idle pause and metadata (resizing is admin-only).","tags":["machines"]}},"/v1/machines/{id}/activity-leases/{leaseID}":{"delete":{"description":"Idempotent. Records completion activity without waking the machine. No Idempotency-Key is needed. API keys: needs one of the scopes `machines:write`, `machines:exec`.","operationId":"deleteActivityLease","responses":{"204":{"description":"Lease released or already absent."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Release a task's activity lease.","tags":["machines"]},"parameters":[{"$ref":"#/components/parameters/MachineID"},{"description":"Caller-chosen task identifier. Reusing it renews the same lease.","in":"path","name":"leaseID","required":true,"schema":{"maxLength":64,"minLength":1,"pattern":"^[A-Za-z0-9_-]+$","type":"string"}}],"put":{"description":"Creates or renews a task lease, waking an idle-suspended machine first. Renew before expiry while working and delete when finished. Expiry starts a full idle period; an explicit lifecycle operation invalidates leases. At most 128 active task leases per machine. No Idempotency-Key is needed. API keys: needs one of the scopes `machines:write`, `machines:exec`.","operationId":"putActivityLease","requestBody":{"content":{"application/json":{"schema":{"properties":{"ttlSeconds":{"default":300,"description":"30-3600 seconds; 0 or absent uses 300.","maximum":3600,"minimum":0,"type":"integer"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityLease"}}},"description":"Task lease created or renewed."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Keep a task's machine awake for a bounded period.","tags":["machines"]}},"/v1/machines/{id}/connection-info":{"get":{"description":"API keys: needs one of the scopes `machines:read`, `machines:ssh`.","operationId":"getConnectionInfo","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionInfo"}}},"description":"Connection info."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"How to reach the sandbox over SSH. No side effects.","tags":["connections"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}]},"/v1/machines/{id}/connections":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"The sandbox must be `running`, else `409 machine_not_running`. The certificate's principal is the sandbox ID and its key ID is the connection ID. API keys: needs one of the scopes `machines:write`, `machines:ssh` (legacy).","operationId":"createConnection","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConnectionRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Connection"}}},"description":"Certificate issued."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/Internal"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Issue a short-lived SSH user certificate for the sandbox.","tags":["connections"]}},"/v1/machines/{id}/desktop":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"A graphical desktop in the machine: Xvfb, an Xfce session, Chromium and a VNC server, which GET /v1/machines/{id}/vnc reaches once it runs. Nothing runs until asked for. The dev, herdr and universal templates include the packages; other templates install them on first use. `status` (the default) reports the state; `install` starts installing the packages in the background (about half a minute, about 1.4 GiB of the root disk) and returns at once; `start` starts the desktop of an installed machine and waits for its VNC server. `width` and `height` size the screen of a desktop that `start` starts (default 1280x800, within 800-2560 x 600-1600). The packages stay installed, and snapshots keep them. `takeover` gives the caller control of the desktop (`controller: human`, `yours: true`): the computer-use MCP in the machine refuses input tools until `handback` returns it to the agent. Control is held per sign-in session (or API key): every call the holder makes renews it, and it returns to the agent 30 s after the holder's last call (a closed viewer), while the same session finds it again after a reload. `handback` by anyone else changes nothing; `takeover` by anyone else takes it over. The lock is advisory: tools driving the display directly (xdotool) do not consult it. API keys: needs one of the scopes `machines:write`, `machines:exec` (legacy).","operationId":"machineDesktop","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DesktopRequest"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Desktop"}}},"description":"The desktop's state."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"The state of a running machine's desktop; install or start it.","tags":["machines"]}},"/v1/machines/{id}/desktop/stream":{"delete":{"description":"Every link minted for this machine so far stops working, and the sessions open through them end within about 3 seconds (each checks every 3 s, on any server instance), or immediately if the check can't be completed. Links minted afterwards work. API keys: needs one of the scopes `machines:write`, `machines:exec` (legacy).","operationId":"revokeMachineDesktopLinks","responses":{"204":{"description":"Revoked."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Revoke every desktop link of the machine and end their open sessions.","tags":["machines"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"Returns a link to the dashboard's full-screen desktop viewer, which anyone with the link can open, or embed in an iframe, without signing in. Expiry stops new connections; revoke ends open sessions: a session opened before `expiresAt` goes on until the viewer leaves, DELETE on this path revokes the link, or the machine or account is deleted. The token in its fragment is signed by the server and binds this machine, its owner and the link's mode; it is not an API key. View-only (the default): the server drops the viewer's input. The machine and its desktop must be running when the link is opened (GET /v1/machines/{id}/vnc rules). API keys: needs one of the scopes `machines:write`, `machines:exec` (legacy).","operationId":"createMachineDesktopLink","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DesktopStreamRequest"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DesktopStream"}}},"description":"The link."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"A short-lived link to watch (or use) the machine's desktop in a browser.","tags":["machines"]}},"/v1/machines/{id}/desktop/stream/state":{"get":{"description":"Unauthenticated: the link's token goes in the `X-Desktop-Token` header (not the query string). Runs the checks GET /v1/machines/{id}/vnc runs for a new connection and always answers 200 with `state`: `ok`; `expired` (expiry stops new connections); `revoked` (DELETE /v1/machines/{id}/desktop/stream ended it); `invalid` (anything else: not a link for this machine, or its owner's account is gone).","operationId":"getMachineDesktopLinkState","parameters":[{"in":"header","name":"X-Desktop-Token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"state":{"enum":["ok","expired","revoked","invalid"],"type":"string"}},"required":["state"],"type":"object"}}},"description":"The link's state."}},"security":[],"summary":"Whether a desktop link would connect now, and if not why.","tags":["machines"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}]},"/v1/machines/{id}/desktop/watch":{"get":{"description":"Server-sent events (`text/event-stream`) for the caller's open desktop session on the machine (GET /v1/machines/{id}/vnc): while that session is open the server runs the desktop's status every 3 s on a guest connection of the heartbeat's own (never the session's), with the caller as the control's owner (the heartbeat that keeps a control the caller holds, which lapses 30 s after the session closes), and sends each result as a `status` event whose data is a Desktop object (the latest one first); a beat the guest did not answer is a `stale` event whose data is `{\"message\": \"...\"}` (the status is stale until the next `status`), and `end` comes when the caller's last desktop session on the machine closes (its sessions, such as two tabs, share one stream of events). A comment line every 15 s keeps the connection alive. 404 `not_found` without an open desktop session of the caller's on the machine: poll POST /v1/machines/{id}/desktop instead. 429 `quota_exceeded` beyond 8 open streams per desktop session. A browser session is checked for the dashboard's origin: an `Origin` header, when the browser sends one (cross-origin), must be the dashboard's; without one (a same-origin `EventSource` carries none) the request is refused only when `Sec-Fetch-Site` or `Referer` says it comes from elsewhere. API keys: needs one of the scopes `machines:write`, `machines:exec` (legacy).","operationId":"watchMachineDesktop","responses":{"200":{"content":{"text/event-stream":{"schema":{"description":"`event: status` lines with a Desktop JSON object as data; `event: stale` with `{\"message\"}` when a heartbeat failed; `event: end` when the caller's last session closes.","type":"string"}}},"description":"The event stream."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/QuotaExceeded"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"The desktop's status, streamed while the caller's VNC session is open.","tags":["machines"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}]},"/v1/machines/{id}/exec":{"parameters":[{"$ref":"#/components/parameters/MachineRef"}],"post":{"description":"Runs `cmd` with `bash -lc` as the sandbox's login user (`dev`, HOME\n`/data/home`, the user SSH sessions get), after exporting `envs` and\nchanging to `cwd` (default HOME, even if the profile changes directory;\na `cwd` that does not exist makes the command fail with exit code 1). `stdin` is fed to the command, then EOF.\n\nThe request blocks until the command exits or `timeoutSeconds` passes.\nOn timeout the command's process group is killed (SIGKILL) and the\nresult has `timedOut: true` and `exitCode: 124` (also when the sandbox\ndid not start the command in time). A command killed by a signal has\n`signal` set and `exitCode` 128+N for standard signal N, otherwise\n255. A non-zero exit code is a `200` result,\nnot an error. stdout and stderr keep their first 1 MiB each\n(`truncated: true` when either was cut); invalid UTF-8 is replaced\nwith U+FFFD.\n\nThe sandbox must be `running` (`409 machine_not_running`). At most a\nfew execs per user run at once (default 4); more get\n`429 quota_exceeded` (`retryable`). If the sandbox cannot be reached,\n`503 host_unavailable`; when the connection is lost after the command\nstarted it is not `retryable`, since the command may have run.\nNot idempotent: no Idempotency-Key, and a retry runs the command again.\nAPI keys: needs one of the scopes `machines:write`, `machines:exec` (legacy).\n","operationId":"execCommand","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecResult"}}},"description":"The command ran (whatever its exit code)."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Run a command in the sandbox and return its output.","tags":["connections"]}},"/v1/machines/{id}/files":{"get":{"description":"Lists a directory or returns a file of a running sandbox you own, read live the same way exec runs commands, as root (sudo; as the dev user if sudo needs a password). The response is the Files shape: a directory lists its entries (directories first, then by name, at most 5000); a regular file returns its content as text (at most 256 KiB) unless it is binary. Symbolic links in the path are followed. A sandbox paused for idleness is resumed first; other non-running sandboxes return 409 `machine_not_running`. A path that does not exist is 404. It counts toward the concurrent exec limit. API keys: needs one of the scopes `machines:write`, `machines:exec` (legacy).","operationId":"getMachineFiles","parameters":[{"description":"Absolute path, at most 4096 bytes (default \"/\").","in":"query","name":"path","schema":{"default":"/","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Files"}}},"description":"The file or directory."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Read a file or directory of a running sandbox.","tags":["machines"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}]},"/v1/machines/{id}/fork":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"Returns a `create` operation whose `machineId` is the new sandbox. The source must be stopped, fully saved (`storage.synced`) and have no operation in progress. The new sandbox gets the fixed size (size fields: as in create) and its own SSH host keys, ID and hostname at its first boot. Forks count toward your limit of 5 sandboxes. The dashboard does not offer forks; the API and CLI do. When no host has room to start it, the operation fails with `no_capacity` and the fork stays, `stopped`; start it later. API keys: needs one of the scopes `machines:write`.","operationId":"forkMachine","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForkMachineRequest"}}},"required":true},"responses":{"202":{"$ref":"#/components/responses/OperationAccepted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Copy a stopped sandbox into a new one (system files and storage).","tags":["machines"]}},"/v1/machines/{id}/images":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"deprecated":true,"description":"The old name of POST /v1/machines/{id}/snapshots; replies in the old shape (DeprecatedImage). API keys: needs one of the scopes `images:write`.","operationId":"createImage","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSnapshotRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeprecatedImage"}}},"description":"The snapshot.","headers":{"Deprecation":{"$ref":"#/components/headers/Deprecation"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Deprecated: POST /v1/machines/{id}/snapshots.","tags":["snapshots"]}},"/v1/machines/{id}/metrics":{"get":{"description":"Samples from `since` (default: 5 minutes ago, at most 24 hours ago) to now, oldest first. One sample per 30 seconds for the times the sandbox was running in that window; none for the times it was paused or stopped, so a sandbox that did not run in the window gets an empty list. API keys: needs the scope `machines:read`.","operationId":"getMachineMetrics","parameters":[{"description":"RFC 3339 time, in the last 24 hours.","in":"query","name":"since","required":false,"schema":{"format":"date-time","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MachineMetrics"}}},"description":"The samples."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/MetricsUnavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"The sandbox's recent CPU and memory, one sample per 30 seconds.","tags":["usage"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}]},"/v1/machines/{id}/orca-connections":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"Returns a single-use tunnel endpoint (valid 60 s), an assertion the\nruntime verifies against its trust file (valid 5 min, `aud` =\n`orca:\u003cmachineId\u003e`), and the runtime's E2EE public key. Every call\nmints a new ticket, so it takes no `Idempotency-Key`; clients call it\nonce per socket. The first call after each sandbox change writes the\ntrust file into the sandbox. Never starts a sandbox: `409\nmachine_not_running`; a template without the `orca` capability: `409\norca_unsupported`; a runtime that is not up yet: `409\nruntime_not_ready` (retryable).\n","operationId":"createOrcaConnection","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrcaConnectionRequest"}}},"required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrcaConnection"}}},"description":"Connection authorized."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/Internal"}},"summary":"Authorize one WebSocket to the sandbox's Orca runtime.","tags":["orca"]}},"/v1/machines/{id}/port-agent":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"API keys require `machines:write`. The installed credential manages only this sandbox's public ports.","operationId":"installPortAgent","responses":{"204":{"description":"Command and sandbox-local credential installed."},"409":{"$ref":"#/components/responses/Conflict"},"503":{"description":"Public ports are not configured."}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Install instabox-port in a running sandbox and rotate its credential."}},"/v1/machines/{id}/ports":{"get":{"description":"API keys require `machines:read`. Also accepts the sandbox-local port credential.","operationId":"listPublicPorts","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPortList"}}},"description":"Public ports and server availability."}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]},{"portAgent":[]}],"summary":"List a sandbox's public ports."},"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"API keys require `machines:write`. Also accepts the sandbox-local port credential.\nAnyone with the returned URL can reach the whole service on that port. The URL\nstays until the port is unpublished and answers only while the sandbox runs;\nstopped or paused, it returns 503. Publishing a public port again keeps its URL\nand sets its name when given. Publishing does not start a server on the port.\nAt most 16 public ports per sandbox.\n","operationId":"publishPort","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishPortRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPort"}}},"description":"The port was already public; its name is updated when given."},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPort"}}},"description":"Port published."},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"description":"The sandbox already has 16 public ports."},"503":{"description":"Public ports are not configured."}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]},{"portAgent":[]}],"summary":"Give one HTTP port of a sandbox a public URL."}},"/v1/machines/{id}/ports/{port}":{"delete":{"description":"API keys require `machines:write`. Also accepts the sandbox-local port credential.","operationId":"unpublishPort","responses":{"204":{"description":"Port unpublished."},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]},{"portAgent":[]}],"summary":"Remove a port's public URL and close its active connections."},"parameters":[{"$ref":"#/components/parameters/MachineID"},{"in":"path","name":"port","required":true,"schema":{"type":"integer"}}]},"/v1/machines/{id}/resume":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"The body is optional: `{\"vault\": false}` turns the Vault off for the\nmachine from now on (docs/vault.md). API keys: needs one of the scopes `machines:write`.\n","operationId":"resumeMachine","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResumeMachineRequest"}}},"required":false},"responses":{"202":{"$ref":"#/components/responses/OperationAccepted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Resume a paused sandbox where it left off.","tags":["machines"]}},"/v1/machines/{id}/snapshots":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"A stopped sandbox (fully saved, no operation in progress): synchronous (`201`) and quick, since no data is copied. A running sandbox, where the server allows live snapshots: `202` with the snapshot `pending`; the sandbox's files are frozen briefly while they are copied, and the snapshot becomes `ready` (or `failed`) once the copy is uploaded. Unless `rootOnly` is true, both system files and storage are saved, including credentials under HOME (/data/home). Memory and running programs are not included. API keys: needs one of the scopes `images:write`.","operationId":"createSnapshot","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSnapshotRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}},"description":"The snapshot (of a stopped sandbox)."},"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}},"description":"The snapshot, pending (of a running sandbox, saved live)."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Save a sandbox's system files and storage as a snapshot.","tags":["snapshots"]}},"/v1/machines/{id}/ssh-tokens":{"delete":{"description":"Revokes the sandbox's SSH tokens that are still valid and closes the\nSSH connections that use them. Certificates are not affected.\nRepeating it revokes nothing more. API keys: needs one of the scopes\n`machines:write`, `machines:ssh` (legacy).\n","operationId":"revokeSSHTokens","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSHTokensRevoked"}}},"description":"Tokens revoked."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Revoke every SSH token of the sandbox and close their connections.","tags":["connections"]},"get":{"description":"Secrets and commands are never returned. History is kept for 30 days after the token's original expiry. Effective expiry includes the issuing API key's expiry. API keys require `machines:read`.","operationId":"listSSHTokens","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSHTokenList"}}},"description":"SSH token metadata, newest first."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"List SSH token metadata, including revoked and recently expired tokens.","tags":["connections"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"Returns a token to use as the SSH user name:\n`command` is `ssh -p \u003cport\u003e \u003ctoken\u003e@\u003chost\u003e`. The token is shown only\nhere (the server keeps its sha256), so this request takes no\n`Idempotency-Key`; a retry makes another token. The sandbox may be in\nany state but `deleting`: while it is not running, a client that logs\nin is told to start it. The token works until `expiresAt`, until\n`DELETE /v1/machines/{id}/ssh-tokens`, until the sandbox is deleted or,\nfor a token an API key made, until the key is revoked or expires.\nAPI keys: needs one of the scopes `machines:write`, `machines:ssh` (legacy).\n","operationId":"createSSHToken","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSSHTokenRequest"}}},"required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSHToken"}}},"description":"Token issued."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Issue a short-lived SSH token, for a plain `ssh` command without a key.","tags":["connections"]}},"/v1/machines/{id}/ssh-tokens/{tokenID}":{"delete":{"description":"Repeated revocation succeeds. Certificates and other tokens are unaffected. API keys require `machines:write` or `machines:ssh` (legacy).","operationId":"revokeSSHToken","responses":{"204":{"description":"Token revoked."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Revoke one SSH token and close its SSH and mosh connections.","tags":["connections"]},"parameters":[{"$ref":"#/components/parameters/MachineID"},{"in":"path","name":"tokenID","required":true,"schema":{"type":"string"}}]},"/v1/machines/{id}/start":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"On a paused sandbox this resumes it with its running programs; if they cannot be brought back it boots fresh (`runtime.processesPreserved` false). Files are kept either way. `host` is admin-only (else `403 permission_denied`). API keys: needs one of the scopes `machines:write`.","operationId":"startMachine","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartMachineRequest"}}},"required":false},"responses":{"202":{"$ref":"#/components/responses/OperationAccepted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Start a stopped or paused sandbox.","tags":["machines"]}},"/v1/machines/{id}/stop":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"`stop` on a paused sandbox returns `409 operation_conflict` (start or delete it). API keys: needs one of the scopes `machines:write`.","operationId":"stopMachine","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StopMachineRequest"}}},"required":false},"responses":{"202":{"$ref":"#/components/responses/OperationAccepted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Stop a running sandbox (shuts it down; running programs end, files are kept).","tags":["machines"]}},"/v1/machines/{id}/suspend":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"API keys: needs one of the scopes `machines:write`.","operationId":"suspendMachine","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"202":{"$ref":"#/components/responses/OperationAccepted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/Internal"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Pause a running sandbox, keeping its running programs.","tags":["machines"]}},"/v1/machines/{id}/terminal":{"get":{"description":"Upgrades to a WebSocket (RFC 6455; send Connection: Upgrade, Upgrade: websocket) carrying a PTY (xterm-256color) of a login shell as the dev user on a running sandbox you own. Binary messages are the terminal's bytes in both directions. Text messages are JSON controls: {\"type\":\"resize\",\"cols\":N,\"rows\":N} from the client; {\"type\":\"session\",\"session\":S,\"persistent\":B} before the shell's output, then {\"type\":\"exit\",\"code\":N} when the shell exits, {\"type\":\"taken\"} when the session was opened elsewhere, or {\"type\":\"error\",\"message\":\"...\"} from the server, which then closes. The shell is a persistent session: it keeps running when the connection ends, and the next terminal on the same session gets it back with its recent output replayed (one viewer at a time). The server pings every 30 s and drops a connection silent for 90 s. A browser session must come from the dashboard's origin (Origin header). At most 8 open terminals per user (429 `quota_exceeded`). API keys: needs one of the scopes `machines:write`, `machines:exec` (legacy).","operationId":"openMachineTerminal","parameters":[{"description":"Initial width in columns, 10-1000 (default 80).","in":"query","name":"cols","schema":{"type":"integer"}},{"description":"Initial height in rows, 4-500 (default 24).","in":"query","name":"rows","schema":{"type":"integer"}},{"description":"Session name, 1-32 of a-z, 0-9 and - (default main).","in":"query","name":"session","schema":{"pattern":"^[a-z0-9-]{1,32}$","type":"string"}},{"description":"0 never wakes an idle-suspended sandbox: one that is not running is 409 (retryable).","in":"query","name":"wake","schema":{"enum":["0"],"type":"string"}}],"responses":{"101":{"description":"Switched to the WebSocket protocol."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Open a terminal on a running sandbox, over a WebSocket.","tags":["machines"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}]},"/v1/machines/{id}/usage":{"get":{"description":"Running time (not paused or stopped) and its vCPU and memory seconds, and disk GiB-seconds over the time the sandbox existed. Default window: the start of the current calendar month (UTC) to now; at most 366 days. Running time is recorded from the release that added this route on. A deleted sandbox's usage is still readable by ID, by the account that created it (not after the account is deleted and signed up again). API keys: needs the scope `machines:read`.","operationId":"getMachineUsage","parameters":[{"in":"query","name":"from","required":false,"schema":{"format":"date-time","type":"string"}},{"in":"query","name":"to","required":false,"schema":{"format":"date-time","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MachineUsage"}}},"description":"The usage."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"What the sandbox used over a window, for billing.","tags":["usage"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}]},"/v1/machines/{id}/vault":{"get":{"description":"Whether the machine takes the Vault, the items that apply to it with\nits current metadata (names, never values), and the last write into\nit. API keys: needs one of the scopes `machines:read`. 404 without a\nVault on the server.\n","operationId":"getMachineVault","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MachineVault"}}},"description":"The machine's Vault."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"What the Vault writes into this machine (docs/vault.md).","tags":["vault"]},"parameters":[{"$ref":"#/components/parameters/MachineRef"}],"post":{"description":"The same write as after a boot or resume, synchronously: the items\nthat apply are written, files of an earlier write that no longer\napply are removed (unless changed in the machine since), and the\nrecord is returned. Takes no `Idempotency-Key` (a repeat writes the\nsame thing again). API keys: needs one of the scopes `machines:write`.\n","operationId":"pushMachineVault","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultApplied"}}},"description":"The write's record (`error` set when the guest refused it)."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Write the current Vault items into the running machine now.","tags":["vault"]}},"/v1/machines/{id}/vnc":{"get":{"description":"Upgrades to a WebSocket (RFC 6455) carrying the VNC (RFB) stream of the machine's desktop: binary messages in both directions, as noVNC speaks. The VNC server listens on the machine's loopback only and has no password: this endpoint is the way in. When the desktop stops, the server closes with code 1001 and the reason \"desktop closed\". 409 `desktop_not_running` before POST /v1/machines/{id}/desktop started it. The server pings every 30 s and drops a connection silent for 90 s. A browser session must come from the dashboard's origin (Origin header). Open desktops count toward the 8 open terminals per user (429 `quota_exceeded`). A desktop link (POST /v1/machines/{id}/desktop/stream) connects with the subprotocols `Sec-WebSocket-Protocol: instabox-desktop.v1, instabox-token.\u003ctoken\u003e` (never the URL; a `token` query parameter is ignored); the server selects `instabox-desktop.v1` only. The token is then the only credential, for that machine only, while the machine and its owner's account exist, until it expires or is revoked (401 otherwise, also without `instabox-desktop.v1` or the token). Expiry stops new connections; revoke ends open sessions (closed with the reason \"link revoked\"). A view-only link's input (keys, pointer, clipboard, resize) never reaches the desktop. API keys: needs one of the scopes `machines:write`, `machines:exec` (legacy).","operationId":"openMachineVNC","responses":{"101":{"description":"Switched to the WebSocket protocol."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"The desktop of a running machine, over VNC in a WebSocket.","tags":["machines"]},"parameters":[{"$ref":"#/components/parameters/MachineID"}]},"/v1/machines/{id}/wake":{"parameters":[{"$ref":"#/components/parameters/MachineID"}],"post":{"description":"Records activity on a running machine, or waits up to 45 seconds for an idle suspend and its resume to finish. Concurrent callers share the operation. A manual stop/suspend, timeout or archive is not undone. No Idempotency-Key is needed. A 409 operation_conflict includes the operation ID if it is still in progress; no workload was executed. API keys: needs one of the scopes `machines:write`, `machines:exec`, `machines:ssh`.","operationId":"wakeMachine","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Machine"}}},"description":"Running machine; activity recorded."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Resume an idle-suspended machine before using it.","tags":["machines"]}},"/v1/me":{"get":{"description":"\"API keys: any key.\" Not behind the invite gate: `inviteRequired` and `activated` tell a client whether the account must redeem an invite code (`POST /v1/invites/redeem`) before the other routes answer.","operationId":"getMe","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}},"description":"The user."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"The signed-in user and the sandbox size they get.","tags":["auth"]}},"/v1/openapi.json":{"get":{"description":"The OpenAPI document of this server, converted to JSON. Public.","operationId":"getOpenAPI","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"The OpenAPI document."}},"security":[],"summary":"This document as JSON.","tags":["docs"]}},"/v1/operations/{id}":{"get":{"description":"API keys: needs one of the scopes `machines:read`, `images:read`.\n\nWith `wait`, a long poll: the response comes as soon as the operation is `succeeded` or `failed`, or its `state` or `phase` differs from what it was when the request arrived, or when the wait ends, whichever is first; it is always the operation as it is then. Loop on it instead of polling at an interval. A done operation answers at once.","operationId":"getOperation","parameters":[{"description":"Long-poll for up to this long: a duration such as `25s` or `500ms`, or whole seconds (`25`). Capped at 30s; absent or `0` answers at once.","in":"query","name":"wait","required":false,"schema":{"example":"25s","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}},"description":"The operation."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Get an operation (the progress of a sandbox change).","tags":["operations"]},"parameters":[{"description":"Operation ID (`op_…`).","in":"path","name":"id","required":true,"schema":{"type":"string"}}]},"/v1/orca/keys":{"get":{"operationId":"getOrcaKeys","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrcaKeys"}}},"description":"Issuer and keys."},"404":{"$ref":"#/components/responses/NotFound"}},"security":[],"summary":"Keys that sign Orca runtime assertions. No authentication.","tags":["orca"]}},"/v1/ports":{"get":{"description":"API keys require `machines:read`. Deleted sandboxes' ports are not listed.","operationId":"listAllPublicPorts","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPortList"}}},"description":"Public ports, by sandbox (oldest first) and port."}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"List the public ports of all your sandboxes."}},"/v1/sessions":{"get":{"operationId":"listSessions","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionList"}}},"description":"Sessions, newest first."},"401":{"$ref":"#/components/responses/Unauthenticated"}},"summary":"List your active sign-ins (CLI and browser sessions).","tags":["sessions"]}},"/v1/sessions/{id}":{"delete":{"description":"Revoking the current browser session also clears its cookie. No Idempotency-Key is needed; repeating it returns 404.","operationId":"revokeSession","parameters":[{"in":"path","name":"id","required":true,"schema":{"pattern":"^ss_[0-9a-f]{24}$","type":"string"}}],"responses":{"204":{"description":"Revoked."},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Sign out one of your sessions (revokes all its tokens).","tags":["sessions"]}},"/v1/snapshots":{"get":{"description":"API keys: needs one of the scopes `images:read`.","operationId":"listSnapshots","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotList"}}},"description":"Your snapshots, oldest first."},"401":{"$ref":"#/components/responses/Unauthenticated"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"List your snapshots.","tags":["snapshots"]}},"/v1/snapshots/{id}":{"delete":{"description":"Sandboxes created from it are not affected. Its data is cleaned up once no sandbox needs it. Only the owner or an admin may delete a snapshot (another user's is 404). API keys: needs one of the scopes `images:delete`.","operationId":"deleteSnapshot","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Delete a snapshot.","tags":["snapshots"]},"get":{"description":"API keys: needs one of the scopes `images:read`.","operationId":"getSnapshot","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}},"description":"The snapshot."},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Get one of your snapshots.","tags":["snapshots"]},"parameters":[{"$ref":"#/components/parameters/SnapshotID"}]},"/v1/snapshots/{id}/files":{"get":{"description":"Reads the snapshot's saved system files directly; no sandbox runs. A directory lists its entries (directories first, then by name, at most 5000); a regular file returns its content as text (at most 256 KiB) unless it is binary. Symbolic links in the path, also the last component, are followed. The saved storage (/data: HOME and your work) is not shown here. After an unclean stop the newest changes may not show. A path that does not exist is 404; system files that cannot be read (not an ext4 filesystem) are 400. API keys: needs one of the scopes `images:read`.","operationId":"getSnapshotFiles","parameters":[{"$ref":"#/components/parameters/FilesPath"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Files"}}},"description":"The file or directory."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Read a file or directory of a snapshot's system files.","tags":["snapshots"]},"parameters":[{"$ref":"#/components/parameters/SnapshotID"}]},"/v1/templates":{"get":{"description":"API keys: needs one of the scopes `machines:read`, `images:read`.","operationId":"listTemplates","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateList"}}},"description":"Templates."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"List the templates a sandbox can start from.","tags":["machines"]}},"/v1/tunnels/{ticket}":{"get":{"description":"The ticket is the credential (WebSocket clients cannot set headers).\nHTTP/1.1 upgrade only. The tunnel closes when the sandbox stops,\npauses or is deleted, and when the session that requested it is\nrevoked.\n","operationId":"openOrcaTunnel","responses":{"101":{"description":"Switching Protocols; frames are relayed unchanged."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"security":[],"summary":"WebSocket upgrade proxied to the sandbox's Orca runtime.","tags":["orca"]},"parameters":[{"description":"`ibt_…` from an OrcaConnection endpoint; single use. Never logged.","in":"path","name":"ticket","required":true,"schema":{"type":"string"}}]},"/v1/usage":{"get":{"description":"Your sandbox and snapshot counts, the stored data they take up, and your limits (5 sandboxes, 200 GB of stored data by default). Stored data is measured about once an hour. API keys: needs one of the scopes `machines:read`, `images:read`.","operationId":"getUsage","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}},"description":"Usage from the last hourly measurement, and the limits."},"401":{"$ref":"#/components/responses/Unauthenticated"}},"security":[{"bearerAuth":[]},{"apiKey":[]},{"sessionCookie":[]}],"summary":"Your storage use and limits.","tags":["usage"]}},"/v1/vault":{"get":{"description":"Values are never returned. Each item says how many of the caller's live\nmachines it applies to. 404 without a Vault on the server (docs/vault.md).\n","operationId":"listVaultItems","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultItemList"}}},"description":"The items."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"summary":"The caller's Vault items, by name. Sign-in sessions only.","tags":["vault"]}},"/v1/vault/{name}":{"delete":{"description":"Machines lose it at their next boot, resume or push (a file item's file is removed then, unless changed in the machine since).","operationId":"deleteVaultItem","responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"summary":"Delete a Vault item. Sign-in sessions only.","tags":["vault"]},"parameters":[{"description":"The item's name. An env item's name is the variable's name.","in":"path","name":"name","required":true,"schema":{"pattern":"^[A-Za-z0-9_.-]{1,128}$","type":"string"}}],"patch":{"operationId":"updateVaultItem","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVaultItemRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultItem"}}},"description":"Updated."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"summary":"Change a Vault item's selector without resending its value. Sign-in sessions only.","tags":["vault"]},"put":{"description":"The whole item is sent, the value included; a replace keeps the ID.\nIdempotent by nature, it takes no `Idempotency-Key`. An env item's\nname must be a variable name and not `HOME`, `PATH`, `USER`, `LOGNAME`,\n`SHELL`, `TERM`, `XDG_RUNTIME_DIR` or `DBUS_SESSION_BUS_ADDRESS`, nor\nstart with `INSTABOX_` or `SSH_`. A file item's path is relative to the\nhome directory, clean, without `..` components, at most 255 bytes, and\nunique among the caller's items (`409 operation_conflict` otherwise).\nAt most `limits.vault.maxItems` items and `maxBytes` of values per user\n(`429 quota_exceeded`).\n","operationId":"putVaultItem","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutVaultItemRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultItem"}}},"description":"Replaced."},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultItem"}}},"description":"Created."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/PermissionDenied"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/QuotaExceeded"}},"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"summary":"Create or replace a Vault item. Sign-in sessions only.","tags":["vault"]}}},"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"servers":[{"url":"https://api.instabox.sh"}],"tags":[{"name":"auth"},{"name":"machines"},{"name":"operations"},{"name":"connections"},{"name":"orca"},{"name":"snapshots"},{"name":"usage"},{"name":"sessions"},{"name":"keys"},{"name":"vault"},{"name":"admin"},{"name":"docs"}]}