Skip to main content
POST
Create a sandbox.

Authorizations

Authorization
string
header
required

Opaque instabox access token (1 hour). Refresh with /v1/auth/refresh (refresh token valid 30 days, rotates on use).

Headers

Idempotency-Key
string
required

Client-generated key, 1-200 printable ASCII characters, scoped to the user. Same key and same method+path+body returns the original response; same key with a different request returns 409 idempotency_conflict. Records are kept for 7 days.

Required string length: 1 - 200
Pattern: ^[\x21-\x7e]+$

Body

application/json

Every field is optional; a body key that is none of these is refused with 400 unknown_field (keys match case-insensitively). The lifetime timeout retired on 2026-10-20 is refused too; timeoutSeconds, onTimeout or timeout with any value but null is 400 unknown_field, and the message names deleteAfterSeconds, maxRunMinutes and autoSuspendAfterSeconds instead (to a User-Agent of instabox-cli/<version>, whose builds before that date sent them, it says the CLI is out of date, how to install the current one, and names the flags --delete-after, --max-run-minutes and --auto-suspend). At most one of templateId and snapshotId; with neither, the sandbox starts from agent-browser, the CLI's and dashboard's default. Leave the size fields out for the default size, or name the vcpus and memMiB of another of your sizes (the org's plan's or one an admin granted the org; GET /v1/orgs allowedSpecs).

name
string

1-63 lowercase letters, digits or -, starting with a letter or digit; unique among the org's sandboxes. Absent or empty: box- and 8 random hex digits (the operation's machineId and the sandbox carry it).

templateId
string

The template to create from (GET /v1/templates, one with creatable true), optionally with a version as id@version (e.g. agent-browser@2026-10-15.4, the same as templateVersion). Absent (and no snapshotId or templateVersion): agent-browser. An unknown or retired one is refused with 400 bad_request whose message lists the templates your plan can create from. The free plan creates from agent-browser, android, omarchy or ci only; another template is refused with 403 template_not_allowed. A create from a snapshot is not limited.

Example:

"agent-browser"

templateVersion
string

Default: the version GET /v1/templates marks latest. templateId may name it instead, as id@version (e.g. agent-browser@2026-10-15.4); both at once must agree. A version the template does not have is 400 bad_request whose message lists its versions, newest first, the latest (or default) one tagged.

snapshotId
string

Create from this snapshot of yours: its system files and storage (empty storage for a system-files-only snapshot).

imageId
string
deprecated

Deprecated: snapshotId under its old name (both: they must be equal). The reply carries a Deprecation header.

vcpus
integer

Absent: the default size (4 vCPUs, 8 GiB memory; limits.machineSpec). Else one of the org's sizes (GET /v1/orgs allowedSpecs: the default, its plan's others and any size an admin granted the org; on api.instabox.sh free 4/8192 only, Pro 2/4096, 4/8192, 8/16384, Builder 2/4096 up to 16/32768, Team and Enterprise 2/4096 up to 32/65536); name memMiB too when two of them have this many vCPUs. Anything else is refused with 400 spec_not_allowed; 0 or a negative (no size) with 400 bad_request.

Required range: x >= 1
memMiB
integer

Like vcpus: the memory (MiB) of one of your sizes, such as 8192 with vcpus 4; 0 or a negative is 400 bad_request.

Required range: x >= 1
persistentDiskGiB
integer

Storage (/data) size in GiB. The disks come with the size: storage gets the size's (20 GiB on api.instabox.sh), or a snapshot's storage size when that is larger. Absent or exactly that size: accepted; any other is 400 spec_not_allowed whose message names the size's storage next to what was asked; 0 or a negative is 400 bad_request. To get more storage, grow it later with PATCH /v1/machines/{id}: a running sandbox's storage grows live, and a shrink applies while it is stopped, at its next boot. The SDKs' diskGiB (TypeScript) and persistent_disk_gib (Python) are this field; diskGiB in a REST body is 400 unknown_field.

Required range: x >= 1
rootDiskGiB
integer

System files size in GiB. Like persistentDiskGiB: system files get the template's size (or the snapshot's); naming any other is 400 spec_not_allowed (the message names the size's root disk), 0 or a negative 400 bad_request; grow it later with PATCH /v1/machines/{id}.

Required range: x >= 1
desiredState
enum<string>

Default: running.

Available options:
running,
stopped
metadata
object

String labels. At most 32 keys of 1-64 characters [A-Za-z0-9_.-], values of at most 512 characters, 8 KiB as JSON.

archiveSeconds
integer

Sets archiveAt to now plus this many seconds: the sandbox is stopped then if it is running. 0 or absent: none.

Required range: 60 <= x <= 86400
deleteAfterSeconds
integer

Sets deleteAt to now plus this many seconds (60-86400, else 400): the sandbox is then deleted, whatever its state, so it cannot outlive a client that died (instabox run sets it). 0 or absent: never. A server without this field ignores it.

Required range: 60 <= x <= 86400
maxRunMinutes
integer

Maximum continuous runtime in minutes. -1 is unlimited; 0 sleeps immediately. Independent of idle auto-pause; whichever condition is reached first pauses the sandbox. Free machines default to 480 and only an admin session can change them. The service manages free runtime and may pause a run earlier. The response reports the current limit from runStartedAt; it can change during the run. Paid machines default to -1 and their users can change this setting. Max-run suspension starts a cooldown, returning 409 machine_cooldown with Retry-After on every wake path. By default it is 5 to 20 minutes (5 plus a random 0 to 15) on a paid plan and for the first such pause in a row on the free plan. Back-to-back pauses on the free plan rest longer (60 to 75, then 120 to 135 minutes): "Longer cooldowns on the free plan" at https://instabox.sh/docs/machines/auto-pause#longer-cooldowns-on-the-free-plan. Once cooldown ends, 0 still refuses wake with 409 max_run_disabled until changed.

Required range: -1 <= x <= 2147483647
autoSuspendAfterSeconds
integer

Absent: 1800 (30 minutes). The plan decides, and a value is honoured or refused, never silently replaced: a paid sandbox takes 300-1800 (5 to 30 minutes) in whole minutes, as sent, and 0 (off) where the plan allows always on (else 403 spec_not_allowed); below 300 (1-299), above 1800, negative or not a multiple of 60 is 400 bad_request. The free sandbox is fixed at 1800: any other value is 403 spec_not_allowed, a plan limit (older servers: 403 permission_denied). A refused create makes no sandbox. The reply's autoSuspendAfterSeconds shows the effective value. Separately, an account an admin gave manual lifecycle (GET /v1/me manualLifecycle) sends 120-86400 idle seconds, or 0 never (docs/idle-suspend.md).

vault
boolean

False disables legacy Vault delivery. Org Secret selections are independent.

secrets

Proxy Secrets from this org. All includes current and future proxy Secrets; [] selects none. Omit both secrets and envSecrets to retain only existing Vault delivery. When envSecrets alone is supplied, proxy selection defaults to all; directSecrets alone selects no proxy Secrets. Null is rejected.

Allowed value: "all"
envSecrets
string[]

Explicit env Secret names from this org; omitted or [] selects none. No all mode; null is rejected. Real values enter the sandbox. Shorthand for directSecrets entries with env = the Secret's name; not sent together with directSecrets.

A unique environment variable name within an org. The names a sandbox's env reserves are reserved (shell variables such as PATH, HOME, BASH_ENV, ENV and IFS, session and trust-store variables, and INSTABOX_ and SSH_ prefixes). A Secret saved under a name before it was reserved is not delivered: the machine's guest status names it.

Pattern: ^[A-Za-z_][A-Za-z0-9_]{0,127}$
directSecrets
object[]

Direct (delivery env) Secrets from this org, each as a variable and/or a home file. Real values enter the sandbox; prefer proxy Secrets for HTTPS API keys. A fork that sends none of secrets, envSecrets and directSecrets copies its source's selection.

Maximum array length: 64
env
object

Environment variables for the whole sandbox: every login shell exports them (exec, SSH, the terminal and what they start), from the first command on and again after each start and resume. Names match ^[A-Za-z_][A-Za-z0-9_]{0,127}$; BASH_ENV, ENV, HOME, HOSTNAME, IFS, LOGNAME, OLDPWD, PATH, PWD, SHELL, SHLVL, USER, bash's readonly BASHOPTS, BASH_VERSINFO, EUID, PPID, SHELLOPTS and UID, the session's TERM, XDG_RUNTIME_DIR and DBUS_SESSION_BUS_ADDRESS, the trust roots SSL_CERT_FILE, SSL_CERT_DIR, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE and NODE_EXTRA_CA_CERTS, and names starting INSTABOX_ or SSH_ are reserved (set them per exec); values have no NUL bytes; names and values at most 32 KiB together. Not on macOS or Windows templates (400). Fixed at create: a fork takes its source's, a create from a snapshot only its own. An exec's envs, a Vault item and an env Secret of the same name win. Not Secrets: anything in the sandbox can read the values, and the sandbox shows only the names (envNames). The values are stored encrypted at rest; a site without the Secret master key refuses env (400 bad_request).

pool
string

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.

guestUser
string

The sandbox's Linux user (Machine.guestUser), so ssh <sandbox> over your own network (Tailscale) logs in as the same name as on your computer. Lowercase letters, digits, _ and -, starting with a letter or _, at most 32 characters; reserved names (root, instabox, the Debian base system's users and groups such as sudo, systemd-*) are refused with 400 invalid_guest_user. Clients send the local username normalized (DOMAIN\alice and alice@corp.example become alice, lowercase, spaces become _) and nothing when that is not a valid name. Absent: user; with snapshotId, the snapshot's user. Ignored (the sandbox gets dev) for templates without the guest-user capability. Fixed at create.

Maximum string length: 32
Pattern: ^[a-z_][a-z0-9_-]{0,31}$
Example:

"alice"

failFast
boolean
default:false

When no host has room for the sandbox right now, refuse the create at once with 503 no_capacity (retryable; nothing is created, there is no operation and the Idempotency-Key is not kept) instead of answering 202 with an operation that then fails with no_capacity. Every other check and the rest of the create are unchanged; a host that had room can still fill up before the operation places the sandbox. The check does not ask for a host with the secret proxy, which a create with secrets or envSecrets needs: if only hosts without it have room, the create is accepted and its operation fails with no_capacity. A create from a snapshot that no host can start now is refused too, instead of being created stopped. A server without this field ignores it, or, if it already refuses unknown body keys, answers 400 unknown_field.

orgId
string

The org the sandbox counts against and is shared with (GET /v1/orgs); you must be a member. Absent: your personal org, or with snapshotId the snapshot's org (a sandbox from a snapshot lives in the snapshot's org; another is 400 bad_request). Secret selections always resolve within this org. The org's sandbox limit and, on the free tier, its monthly included usage apply (429 quota_exceeded, 402 credit_exhausted).

Response

The mutation was accepted (or replayed for the same idempotency key). Poll the operation.

The progress of a change to a sandbox (suspend is pause; restore puts its disks back to a snapshot's; move, admin only, copies it to another host pool). It does not return the request it was queued with (a start's vcpus/memMiB show in the sandbox's spec).

id
string
required
machineId
string
required
type
enum<string>
required
Available options:
create,
start,
stop,
suspend,
resume,
delete,
restore,
resize,
move
state
enum<string>
required

succeeded and failed are terminal.

Available options:
queued,
running,
succeeded,
failed
error
object | null
required
createdAt
string<date-time>
required
updatedAt
string<date-time>
required
phase
string

Free text progress, e.g. scheduling, booting, waiting_ready, snapshotting.

forkInherited
boolean

On the answer to POST /v1/machines/{id}/fork: the control host that took it keeps the source's delete deadline, idle auto-suspend, max run and metadata, and takes autoSuspendAfterSeconds and maxRunMinutes. Absent: a host from before that (during a rolling control deploy), which gave the fork its defaults with only the request's metadata and deleteAfterSeconds; set the idle auto-suspend and max run with PATCH /v1/machines/{id}.

trigger
enum<string>

Set when instabox started the operation itself: idle (paused when idle), max_run (maximum continuous runtime), memory_pressure (paused, its processes kept, because the host it ran on was running out of memory; the next use wakes it, as after idle), request (wake on use), wake (POST /v1/machines/{id}/wake), timeout, archive, delete_at (the sandbox's deleteAt passed), drain (maintenance by an admin), user_delete (an admin deleted the owner), user_disable (an admin disabled the owner), admin (an admin stopped or paused the sandbox), admin_delete (an admin deleted the sandbox), credit (its free-tier org used up the month's included usage), spend_cap, downgrade, subscription_ended, payment_method (its free workspace has no card on file), members (its free workspace has more members than the free plan allows), early_allowance_ended (paused because the account's early free-sandbox allowance ended and it has more free sandboxes than the free plan runs), abuse_pool (paused because the sandbox tried to connect to a blocked destination on the egress blocklist, such as a crypto-mining pool; a DNS lookup of a listed domain never suspends) or abuse_cpu (paused by the CPU abuse detector: a restricted account's sandbox ran flat out for an hour with no memory use, no public port, nobody using it and a connection to a mining port) or abuse_scan (paused by the flow detector: a restricted account's sandbox swept one port across many hosts, scanned many ports of one host or brute-forced a login port). Absent: a client request.

Available options:
idle,
max_run,
memory_pressure,
timeout,
archive,
delete_at,
drain,
request,
wake,
user_delete,
user_disable,
admin,
admin_delete,
credit,
spend_cap,
downgrade,
subscription_ended,
payment_method,
members,
early_allowance_ended,
abuse_pool,
abuse_cpu,
abuse_scan,
abuse_proxy
machine
object

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.