SB-Box gives your code and your agents an isolated Linux machine on demand. Each sandbox is a full virtual machine with its own kernel, restored from a template snapshot in under half a second, frozen when it goes quiet (only its disk keeps billing, at the disk rate) and resumed with its memory intact.

Three building blocks:

What it is Where it is explained
Template A booted, snapshotted environment: an image plus what should already be running. Build it once. §13 Templates
Sandbox A running machine restored from a template. Run commands, move files, expose ports. §3 Sandbox lifecycle
Freeze & versions A frozen sandbox keeps disk and memory; only its disk bills. Every freeze is a save point you can fork or roll back to. §4 Persistence, §5 Versions

Three surfaces, same objects:

Surface Best for
Console (this site: Templates, Sandboxes, Usage) building templates, browsing sandboxes, an in-browser shell, usage
gsk sb-box CLI scripts, agents and quick manual work from any machine
REST API https://www.genspark.ai/api/sb-box/v1 (beta, §15) your own integrations

The responses shown are real (ids and timestamps shortened).


1. Quickstart

1.1 Running your first sandbox

1. Sign in to Genspark and open the SB-Box console.

2. Get your API key. Install the CLI and log in once; the saved gsk-... key serves both the CLI and the REST API.

npm install -g @genspark/cli
gsk login          # browser login; key saved to ~/.genspark-tool-cli/config.json
gsk login-info     # shows the account, plan and credit balance

3. Pick a template — or don't. gsk sb-box create with no --template starts from the newest public base (Ubuntu 24.04, Python 3 with numpy / pandas / matplotlib, Node 22, git). REST has no default: it needs templateName or templateId.

gsk sb-box templates
# → {"templates": [{"id": "tpl-7b7c…", "name": "sb-box-base-<sha>-<date>",
#                    "namespace": "public", "status": "ready", "can_create": true, …}, …]}

4. Create a sandbox, run a command, kill it.

gsk sb-box create --timeout 600            # public base; --template <name|tpl-id> for another
# → {"id": "sbx-9eb7…", "state": "running", "timeoutAt": "…T11:47:08Z",
#    "cpuCount": 2, "memoryMB": 4096, "diskSizeGB": 20, "idleTimeout": 600, "region": "westus2",
#    "portUrl": "https://{port}-sbx-9eb7….sandbox.westus2-prod.sspark.ai", "domain": "sandbox.westus2-prod.sspark.ai"}

gsk sb-box exec --sandbox_id sbx-9eb7… --command 'echo hello from gsk'
# → {"stdout": "hello from gsk\n", "stderr": "", "exitCode": 0}

gsk sb-box kill --sandbox_id sbx-9eb7…
# → {"status": "killed", "sandbox_id": "sbx-9eb7…"}

The CLI prints one JSON envelope per call: {"version": 1, "status": "ok", "message": "success", "data": …}. In scripts, take .data:

ID=$(gsk sb-box create --timeout 600 | jq -r .data.id)

The same four steps over REST:

API=https://www.genspark.ai/api/sb-box/v1
H='X-Api-Key: gsk-...'

curl -s "$API/templates" -H "$H" | jq '.templates[] | select(.status=="ready") | .name'

# "base" always means the newest ready public base (its catalog names are dated and roll)
SBX=$(curl -s -X POST "$API/sandboxes" -H "$H" -H 'Content-Type: application/json' \
  -d '{"templateName": "base", "timeout": 600}' | jq -r .id)

curl -s -X POST "$API/sandboxes/$SBX/exec" -H "$H" -H 'Content-Type: application/json' \
  -d '{"command": ["/bin/sh", "-c", "echo hello; uname -r"]}'
# → {"stdout":"hello\n6.18.15\n","stderr":"","exitCode":0}

curl -s -X DELETE "$API/sandboxes/$SBX" -H "$H"
# → {"status":"killed"}

1.2 Upload and download files

Files move in and out by path. The default user is user and its home is /home/user; relative paths resolve there. Parent directories are created.

gsk sb-box write --sandbox_id $ID --path /home/user/data/in.txt --content 'hello from the guide'
# → {"written": 20, "path": "/home/user/data/in.txt"}
gsk sb-box read  --sandbox_id $ID --path /home/user/data/in.txt
# → {"content": "hello from the guide", "path": "/home/user/data/in.txt"}
gsk sb-box ls    --sandbox_id $ID --path /home/user
# → [{"name": "data", "path": "/home/user/data", "isDir": true, "size": 4096}, …]

Binary files go through REST (the body is the file, byte for byte):

curl -s -X POST "$API/sandboxes/$SBX/files/stream?path=/home/user/model.bin" -H "$H" \
  --data-binary @model.bin                       # → {"written":3000}
curl -s "$API/sandboxes/$SBX/files/stream?path=/home/user/out.png" -H "$H" -o out.png

Upload a local text file with the CLI: --content "$(cat local.txt)". If the content has $, quotes or newlines the shell would mangle, put the parameters in a JSON file and pass --args-file (§14).

1.3 Install custom packages

Two ways:

  • At runtime, in the sandbox you already have. Fast to try, gone when the sandbox is killed, and every new sandbox pays the install again.

    gsk sb-box exec --sandbox_id $ID --command 'pip install -q requests && python3 -c "import requests; print(requests.__version__)"'
    
  • In a template, once. Every sandbox created from it starts with the packages installed and the runtime warm — use this for anything you will start more than a few times (§13).

    gsk sb-box build-template --template py-analysis --image python:3.12-slim \
      --cpu_count 2 --memory_mb 2048 \
      --start_cmd 'pip install -q pandas matplotlib && touch /tmp/ready && sleep infinity' \
      --ready_cmd 'test -f /tmp/ready'
    # → {"templateId": "tpl-…", "status": "building", "region": "westus2"}
    gsk sb-box templates          # … py-analysis  ready   (a few minutes later)
    gsk sb-box create --template py-analysis --timeout 600
    

2. API key and authentication

One key drives the CLI and the REST API. Send it in either header:

X-Api-Key: gsk-...
Authorization: Bearer gsk-...

Keep it secret: it acts as you. gsk login stores it in ~/.genspark-tool-cli/config.json; GSK_API_KEY in the environment overrides the file. A call without a valid key answers 401 Invalid or expired API token.

An account without SB-Box access gets 403 {"detail": "SB-Box is not enabled for this account"} on every call.

SB-Box is available on paid plans (Plus, Pro, Team, Enterprise). A Free account gets 403 {"detail": {"code": "sb_box_plan_required", "plan": "free", "message": "…", "upgrade": "https://www.genspark.ai/pricing"}} on every call that creates or runs something (create, resume, clone, reset, build, commands, files). Reads, freeze, kill and template delete still work (billing §2).


3. Sandbox lifecycle

A sandbox is created from a template, runs until its deadline or until it idles, is frozen (disk and memory kept; only the disk bills), resumes any number of times, and ends when you kill it.

3.0 States and transitions

template create starting running freezing frozen restored freeze · idle timer onTimeout: freeze resume · any exec / file / port call · rollback · reset (a re-boot through starting) disk kept; memory snapshot 14 days (§4.1), then only the owner's reset revives it exited guest init exits resume · rollback failed restore fails reset · rollback killed terminal — your kill: 404 afterwards; a timeout or failure kill stays readable as killed kill · onTimeout: kill from any live state

Solid boxes are live states; the dashed box is terminal. Arrows are calls you make or platform timers.

state What it means Machine Billed Leaves via
starting Snapshot being restored (a create, cold resume, rollback or reset); well under a second. allocating no → running, or failed if the restore cannot complete
running Booted guest: commands, files, ports and the interpreter work. held yes freeze / idle timer / onTimeout: freeze → freezing; guest init exits → exited; kill / onTimeout: kill → killed
freezing Memory and disk are being saved; usually a few seconds. releasing no → frozen
frozen Snapshot on storage, machine released, the previous snapshot archived as a version. Resume takes about a second. Memory snapshot kept 14 days untouched (§4.1). none disk only resume, any exec / file / terminal call, or an HTTP request to a port URL → starting → running; rollback → starting; clone leaves it untouched; kill → killed
exited The guest's init process ended on its own. The snapshot is preserved. none disk only resume → starting → running (a fresh init); rollback; kill
failed The sandbox could not be restored or run. none no reset or rollback → starting; kill
killed Terminal: kill, the lifetime deadline with onTimeout: kill, or a sandbox that could not be kept running. Your own kill makes the id 404; a kill by timeout or failure leaves the record readable as killed for a while: GET (sandbox, grants) and kill (no-op 200) answer, acting calls answer 409, reset / rollback 410. none no —
unknown Only in list rows: the sandbox's region could not be read just now. — — —

Rules of thumb:

  • Only running bills compute; frozen and exited bill the disk; every other state bills nothing (billing §1). Frozen sandboxes do not count against the concurrency cap either.
  • Three things freeze: an explicit freeze, the idle timer (10 quiet minutes), and the lifetime deadline with onTimeout: freeze (the default; a deadline kills only with onTimeout: kill, REST only) (§3.2).
  • Every freeze is a save point: the snapshot it replaces is archived as the next version (the first freeze files the template baseline as v1), so frozen is where clone --version and rollback operate (§5).
  • frozen resumes itself: you rarely call resume — the first exec, file, terminal or port request wakes the machine (about 4 s end to end for that first call) and the state passes through starting to running (§4).
  • killed is final: disk and versions are gone; there is no un-kill.

3.1 Create

gsk sb-box create --timeout 1800           # public base; --template <name|tpl-id> for another
curl -s -X POST "$API/sandboxes" -H "$H" -H 'Content-Type: application/json' -d '{
  "templateName": "base",                  # or your own template's name, or "templateId": "tpl-…" (one required)
  "timeout": 1800,                         # seconds; omit = no deadline (the idle timer still applies)
  "lifecycle": {"onTimeout": "freeze"},    # freeze (default) | kill
  "metadata": {"purpose": "docs", "userId": "u-123"},
  "network": {"allowInternetAccess": true} # see §10
}'
Field Type · default Meaning
templateId string Exact template id (tpl-…). One of templateId / templateName is required (400 otherwise).
templateName string Template name; resolves to the newest ready template of that name you can see (your own, shared with you, or the public catalog). base is an alias for the newest ready public base (sb-box-base-<sha>-<date>). Wins over templateId when both are sent.
templateNamespace string · omit Restrict the name lookup to one catalog: public for the shared catalog, or omit to search everything you can see. Any other value answers 400.
timeout number (seconds) · none Lifetime. Omitted or 0 = no deadline. The older timeoutMs (milliseconds) still works but answers with Deprecation: true and a Warning: 299 header.
lifecycle.onTimeout "freeze" | "kill" · freeze What happens at the deadline. gsk sb-box create always sends kill.
metadata object of strings · {} Free-form key/values, echoed back on every read (§6).
envVars object of strings · {} Sandbox-wide environment: exported into every command the sandbox runs; per-command envVars layer on top (§7).
network object · omit Egress and public-URL policy: allowInternetAccess, allowOut, denyOut, allowPublicTraffic, maskRequestHost (§10).
namespace string · omit A grouping label of your choosing (letters, digits, _ - .). Stored as <your user id>/<label> on the record and usable as the namespace filter of GET /sandboxes. Without it each sandbox gets its own generated label.

Response:

{"id": "sbx-44fb…", "templateId": "tpl-7b7c…", "state": "running", "region": "westus2",
 "portUrl": "https://{port}-sbx-44fb….sandbox.westus2-prod.sspark.ai", "domain": "sandbox.westus2-prod.sspark.ai",
 "createdAt": "2026-09-21T11:32:30Z", "startedAt": "2026-09-21T11:32:31Z",
 "timeoutAt": "2026-09-21T11:47:30Z",
 "cpuCount": 2, "memoryMB": 4096, "diskSizeGB": 20,
 "idleTimeout": 600,
 "metadata": {"purpose": "docs", "userId": "u-123", "cogenId": "…"}}

A create takes about half a second over REST (a few seconds via the CLI). Parallel creates, resumes and clones of one account are serialised; one may wait up to 10 s.

3.2 Timeouts: the lifetime and the idle timer

Two independent timers:

Timer Set by Default What happens
Lifetime timeout on create; POST /sandboxes/{id}/timeout later none (REST and gsk create) at timeoutAt the sandbox is frozen (onTimeout: freeze, the default) or killed (kill, REST only).
Idle the platform 600 s no guest activity (network traffic, CPU) for this long → automatic freeze, landing up to 5 minutes later than the timeout — expect a quiet sandbox to be frozen 10 to 15 minutes after its last activity. Reported as idleTimeout on every read; not a request field.

Change the lifetime while the sandbox runs. The value is seconds from now:

gsk sb-box timeout --sandbox_id $ID --timeout 1800     # → {"status": "ok"}
curl -s -X POST "$API/sandboxes/$SBX/timeout" -H "$H" -H 'Content-Type: application/json' -d '{"timeout": 1800}'

GET /sandboxes/{id} then shows the new timeoutAt. Resuming or rolling back with {"timeout": 600} also re-grants a lifetime from now (§4).

Clear an existing deadline with {"timeout": 0}, {} or an empty body — the sandbox then runs until the idle timer freezes it.

3.3 Get information

gsk sb-box get --sandbox_id $ID
curl -s "$API/sandboxes/$SBX" -H "$H"

Fields:

Field Meaning
id The sandbox id (sbx-…); what every other call takes.
templateId The template it was restored from. On a fork this reads <source id>-clone or <source id>-clone-v<N>.
state Live state (§3.0).
region The region it runs in (also present as region on templates).
portUrl, domain Where its ports are reachable: portUrl is the public URL with a literal {port} placeholder (https://{port}-sbx-….sandbox.westus2-prod.sspark.ai), domain the host suffix (sandbox.westus2-prod.sspark.ai) for <port>-<id>.<domain> construction (§10.1).
createdAt, startedAt When it was created; when the current run started (a resume resets startedAt). The create / resume response shows sub-second precision; a later GET shows whole seconds.
frozenAt When the current save point was frozen (the state a resume or rollback restores); set by every freeze, kept while running.
timeoutAt The lifetime deadline; absent when there is none.
cpuCount, memoryMB, diskSizeGB Provisioned size (what is billed, billing §1).
idleTimeout The idle-freeze timeout in force, seconds (platform-set, 600).
versions[] Archived freeze points {version, frozenAt, label}; present once the sandbox has been frozen (§5).
metadata Your key/values plus cogenId (your account id) and, on a fork, clonedFrom (plus clonedVersion when forked from an archived version).
trafficAccessToken Only on the create response, only when network.allowPublicTraffic is false (§10.1).
source, namespace, is_owner, has_write, cogen_id, template_id, created_at, updated_at Also returned by GET /sandboxes/{id}: the namespace label, whether you own it or hold a write grant, and snake_case copies of the ids and timestamps.

3.4 List

gsk sb-box list
curl -s "$API/sandboxes?state=running,frozen" -H "$H"
# → {"sandboxes": [{"id": "sbx-…", "template_id": "tpl-…", "state": "running", "region": "westus2",
#                   "portUrl": "https://{port}-sbx-….sandbox.westus2-prod.sspark.ai", "domain": "sandbox.westus2-prod.sspark.ai",
#                   "source": "user", "created_at": "…", …}], "hasMore": false}

Lists what you created, newest first. Rows are compact; GET /sandboxes/{id} has the full record. No pagination yet (hasMore is always false). Sandboxes shared with you: GET /sandboxes/shared.

Query Meaning
state Comma-separated list of states to keep: running, starting, freezing, frozen, exited, killed. Omit for all. killed rows are sandboxes killed by a timeout or a failure; your own kills leave the list at once.
namespace Only sandboxes created with that namespace label — pass the label you chose (ctest) or its stored form <your user id>/ctest; both match.

3.5 Kill

gsk sb-box kill --sandbox_id $ID                       # → {"status": "killed", "sandbox_id": "sbx-…"}
curl -s -X DELETE "$API/sandboxes/$SBX" -H "$H"        # → {"status":"killed"}

Irreversible and owner-only. Afterwards the id answers 404 {"detail": "sandbox not found"}.


4. Persistence: freeze and resume

A freeze snapshots memory and disk to storage and releases the machine: files, running processes, open sockets and shell history all survive. While frozen only the disk bills (billing §1) and the sandbox does not count against your concurrency cap. The memory snapshot is kept for 14 days of inactivity (§4.1); the disk is kept, and billed, until you kill the sandbox.

gsk sb-box freeze --sandbox_id $ID
# → {"id": "sbx-…", "state": "frozen", "frozenAt": "…", "versions": [{"version": 1, …}]}   # a few seconds
curl -s -X POST "$API/sandboxes/$SBX/freeze" -H "$H"
# → 200 the sandbox record with "state": "frozen"                              the common case
#   202 {"id":"sbx-…","state":"freezing","status":"freeze in progress"}         only if it takes longer than ~50 s
#   200 {"id":"sbx-…","state":"frozen","status":"already frozen"}              when it already was

On a 202 poll get until frozen before you fork or roll back. Repeating the call during a freeze is harmless.

Resume brings it back, processes and memory intact, in about a second:

gsk sb-box resume --sandbox_id $ID
curl -s -X POST "$API/sandboxes/$SBX/resume" -H "$H" -H 'Content-Type: application/json' -d '{"timeout": 600}'
# → the sandbox record, "state": "running", "timeoutAt" re-granted 600 s from now

Resume is idempotent: on a running sandbox it answers 200 and changes nothing (the optional timeout still re-grants the lifetime).

You rarely need to resume by hand. exec, files, background commands, the terminal and HTTP requests to a port URL (§10) all resume a frozen sandbox first (about 4 s end to end for that first call).

Besides freeze (the console's Freeze button is the same call), the idle timer and onTimeout: freeze freeze a sandbox — and so does a zero credit balance (billing §1).

4.1 Memory snapshots expire after 14 days frozen

A frozen sandbox holds its disk (files) for as long as it exists and its memory snapshot (processes, sockets, interpreter variables, shell history) for 14 days from the freeze. Any touch — resume, exec, a file call, a port-URL request — restarts the clock at the next freeze.

What changes once the memory is reclaimed:

State Still frozen; the record gains metadata.memoryExpired: "true".
Disk Intact, every file as it was at the freeze. It keeps billing at the disk rate until you kill the sandbox.
Memory Gone: processes, open connections, interpreter state, environment of the running shell. Archived freeze points (versions) are cleared too — there is nothing to roll back to.
Refused calls resume — explicit, or the automatic one behind exec, files, the terminal and port URLs — answers 428 with {"reason": "memory_expired", "recovery": ["reset"]} (top level, not under detail). rollback, clone and snapshot-template answer 428 as well.
Recovery POST /sandboxes/{id}/reset (§15.3; REST only, the CLI has no reset verb): a cold boot from the disk — the template's start command runs again, files are where you left them, processes and memory state start from zero. The sandbox is running afterwards; its next freeze clears the flag. Reset is owner-only (a grantee's call answers 404, §12) — a write grantee must ask the owner.
curl -s -X POST "$API/sandboxes/$SBX/exec" -H "$H" -H 'Content-Type: application/json' -d '{"command": ["ls"]}'
# → 428 {"reason": "memory_expired", "recovery": ["reset"]}
curl -s -X POST "$API/sandboxes/$SBX/reset" -H "$H"
# → the sandbox record, "state": "running"; files intact, processes restarted

To keep running state longer than two weeks untouched, resume the sandbox (any exec will do) before the window closes, or keep the state you care about on disk.


5. Versions: fork and roll back

Every freeze archives the snapshot it replaces as a version — memory, processes and disk, not just files. A sandbox keeps its newest 5 versions; older ones are deleted, and rollback / clone to a pruned version answers 404 — after six freezes, version 1 (the template baseline) is gone.

gsk sb-box versions --sandbox_id $ID
# → {"sandbox_id": "sbx-…", "state": "frozen",
#    "versions": [{"version": 1, "frozenAt": "…T11:33:10Z", "label": "auto"},
#                 {"version": 2, "frozenAt": "…T11:34:44Z", "label": "auto"}]}

The first freeze archives the template baseline as version 1; the state you froze first becomes version 2 at the next freeze, and so on. GET /sandboxes/{id} carries the same list as versions[].

5.1 Fork

FORK=$(gsk sb-box clone --sandbox_id $ID --version 2 | jq -r .data.id)
gsk sb-box exec --sandbox_id $FORK --command 'cat /home/user/marker.txt'   # the file as it was at v2
curl -s -X POST "$API/sandboxes/$SBX/clone" -H "$H" -H 'Content-Type: application/json' \
  -d '{"version": 2, "metadata": {"purpose": "try-the-migration"}}'
# → {"id": "sbx-6d4a…", "state": "running", "timeoutAt": "<24 h from now>",
#    "metadata": {"clonedFrom": "sbx-44fb…", "clonedVersion": "2", "purpose": "try-the-migration", …}}

A fork is a new sandbox booted from that save point, memory included; the source is untouched and can be in any state. The fork starts running with a 24-hour deadline (adjust with /timeout); its metadata records clonedFrom, plus clonedVersion when taken from an archived version.

clone with no version forks the live state of a running source (its memory is snapshotted and the source resumes) or the active snapshot of a frozen one.

5.2 Roll back

gsk sb-box freeze --sandbox_id $ID        # rollback needs a settled sandbox
gsk sb-box rollback --sandbox_id $ID --version 2
# → the sandbox record, "state": "running": the machine is back at v2
gsk sb-box versions --sandbox_id $ID      # the state you just left was archived first
curl -s -X POST "$API/sandboxes/$SBX/rollback" -H "$H" -H 'Content-Type: application/json' \
  -d '{"version": 2, "timeout": 1800}'   # version 0 = the last committed freeze; timeout optional

The sandbox returns to freeze point N and resumes; the state being left is archived first, so a rollback is itself reversible. On a running sandbox it answers

409 {"detail": "rollback requires a settled snapshot state (frozen/failed/exited), sandbox \"sbx-…\" is running"}

6. Metadata

Attach string key/values at create time; they come back on every read.

curl -s -X POST "$API/sandboxes" -H "$H" -H 'Content-Type: application/json' \
  -d '{"templateId": "tpl-7b7c…", "timeout": 600, "metadata": {"userId": "u-123", "purpose": "docs"}}'
# → … "metadata": {"userId": "u-123", "purpose": "docs", "cogenId": "…"}

Added automatically: cogenId (your account id); clonedFrom and clonedVersion on a fork. Metadata is not a list filter. clone accepts its own metadata for the fork. The CLI does not expose metadata; use REST.


7. Environment variables

Pass variables per command; they are visible to that command only:

curl -s -X POST "$API/sandboxes/$SBX/exec" -H "$H" -H 'Content-Type: application/json' \
  -d '{"command": ["/bin/sh", "-c", "echo $MODE"], "envVars": {"MODE": "demo"}}'
# → {"stdout":"demo\n","stderr":"","exitCode":0}

The same envVars field exists on background commands and the terminal (§8). A plain exec sees the guest's own environment (HOME=/home/user, PATH=…, PWD=/home/user).

envVars on create is the sandbox-wide environment: every exec, background command and terminal sees it, a fork inherits it, and per-command envVars layer on top. Settings every sandbox needs belong in the template (a Dockerfile ENV, or a profile file the start command writes).


8. Commands

8.1 Run a command

gsk sb-box exec --sandbox_id $ID --command 'python3 -c "print(6*7)"'
# → {"stdout": "42\n", "stderr": "", "exitCode": 0}
curl -s -X POST "$API/sandboxes/$SBX/exec" -H "$H" -H 'Content-Type: application/json' -d '{
  "command": ["/bin/bash", "-lc", "cd /home/user && python3 main.py"],
  "cwd": "/home/user",
  "envVars": {"DEBUG": "1"},
  "user": "root",
  "timeout": 120
}'
# → {"stdout":"…","stderr":"…","exitCode":0}
Field Meaning
command An argv array; a bare string is rejected (400 command must be a non-empty list). Use ["/bin/sh", "-c", "…"] for shell syntax. The CLI's --command is wrapped in /bin/sh -c for you.
cwd Working directory; default the user's home.
envVars Variables for this command (§7).
user Omit for the image's default user (user, uid 1000) or root.
timeout Seconds; default 60 (CLI: 30), capped at 600.

A non-zero exit is a normal 200:

{"stdout":"out\n","stderr":"err\n","exitCode":3}

Output is capped at 10 MiB per stream. A command that runs past timeout is killed and the call answers 408 with "killed": true and whatever output arrived (§15.4). Use a background command for anything long.

8.2 Stream output

POST /sandboxes/{id}/sse/exec takes the same body as exec and answers with Server-Sent Events:

curl -N -X POST "$API/sandboxes/$SBX/sse/exec" -H "$H" -H 'Content-Type: application/json' \
  -d '{"command": ["/bin/sh", "-c", "echo one; sleep 1; echo two >&2; exit 2"]}'
event: execId
data: {"execId":"gp-stream-1789990365519283415"}

id: 1
event: stdout
data: {"data":"one\n"}

id: 2
event: stderr
data: {"data":"two\n"}

event: exit
data: {"exitCode":2}

8.3 Background commands

Start a process and come back for it later. A client disconnect never kills a background command; only kill (or the sandbox ending) does.

# start → execId + pid
curl -s -X POST "$API/sandboxes/$SBX/commands/start" -H "$H" -H 'Content-Type: application/json' \
  -d '{"command": ["python3", "-m", "http.server", "8000", "--directory", "/home/user"], "cwd": "/home/user", "envVars": {"A": "1"}}'
# → {"execId":"bg-1789990375798611452","pid":75}

curl -s "$API/sandboxes/$SBX/commands" -H "$H"                         # list
# → [{"execId":"bg-1789990375798611452","pid":75,"sandboxId":"sbx-…"}]

curl -s -X POST "$API/sandboxes/$SBX/commands/$EXEC/input" -H "$H" -H 'Content-Type: application/json' \
  -d '{"data": "y\n"}'                                                  # → {"written":2}
curl -s -X POST "$API/sandboxes/$SBX/commands/$EXEC/close" -H "$H"      # stdin EOF → {"status":"ok"}
curl -s -X POST "$API/sandboxes/$SBX/commands/$EXEC/wait"  -H "$H"      # blocks → {"exitCode":0}
curl -s -X POST "$API/sandboxes/$SBX/commands/kill" -H "$H" -H 'Content-Type: application/json' \
  -d '{"pid": 75, "signal": 15}'                                        # → {"status":"ok"}
Call Body Meaning
commands/start command[] (argv, required), cwd, envVars, user start the process; returns execId (for input / close / wait) and pid (for kill / send-stdin)
commands (GET) — processes started in this sandbox that have not been waited on yet
commands/{execId}/input data (string) write to the process's stdin; returns {"written": <bytes>}
commands/{execId}/close — close stdin (EOF)
commands/{execId}/wait — block until the process exits; returns {"exitCode": n}
commands/kill pid (required), signal (number, default 9) send a signal; 15 for a graceful stop
commands/send-stdin pid, data write to stdin by pid instead of execId

An entry stays in the list until you wait on it, even after the process has exited or been killed. Output is not streamed back from a background command: redirect it to a file and read the file.

8.4 Interactive terminal (PTY)

The console's Shell tab is a PTY. Over REST:

curl -s -X POST "$API/sandboxes/$SBX/pty" -H "$H" -H 'Content-Type: application/json' \
  -d '{"command": ["/bin/bash"], "rows": 24, "cols": 80, "cwd": "/home/user", "envVars": {"PS1": "$ "}}'
# → {"execId":"pty-1789990381393509857","pid":81}

curl -s -X POST "$API/sandboxes/$SBX/pty/$PTY/input"  -H "$H" --data-binary $'echo pty-works\n'   # raw bytes
curl -N "$API/sandboxes/$SBX/pty/$PTY/connect" -H "$H"        # SSE: event: data / data: {"data": "…"}
curl -s -X POST "$API/sandboxes/$SBX/pty/$PTY/resize" -H "$H" -H 'Content-Type: application/json' -d '{"rows": 40, "cols": 120}'
curl -s -X POST "$API/sandboxes/$SBX/pty/$PTY/kill"   -H "$H"
Call Body Meaning
pty (POST) command[] (default ["/bin/bash"]), rows (24), cols (80), cwd, envVars open a terminal; returns execId and pid
pty/{execId}/input raw bytes keystrokes, including the trailing \n for Enter
pty/{execId}/connect (GET) — Server-Sent Events, event: data / data: {"data": "…"} with the terminal bytes; ends with event: exit carrying exitCode
pty/{execId}/resize rows, cols in characters
pty/{execId}/kill — end the session
ws/pty (WebSocket) ?api_key=gsk-… (or the X-Api-Key header) both directions on one connection, for a real terminal client

The session runs with TERM=xterm-256color; colours and escape sequences work.

8.5 Run code on the stateful interpreter

Every sandbox created from a public base dated 2026-10-13 or later (sb-box-base-<sha>-2026-10-13 onwards) carries a code interpreter (a Jupyter Python kernel), started on the first call (about 5 s once per sandbox; later cells about 2 s). Variables, imports and DataFrames persist between calls like a notebook and survive freeze → resume. (-s "$ID" below can be --session <name> — §14.1.)

gsk sb-box run-code -s "$ID" --code 'import pandas as pd
df = pd.DataFrame({"region": ["east", "west"], "margin": [0.31, 0.12]})
df'
# → data = {"stdout": "", "stderr": "", "executionCount": 1, "complete": true, "error": null,
#           "results": [{"text": "  region  margin\n0   east    0.31\n1   west    0.12",
#                        "html": "<div>…</div>", "data": {"region": ["east","west"], "margin": [0.31, 0.12]},
#                        "isMainResult": true}]}
gsk sb-box run-code -s "$ID" --code 'import matplotlib.pyplot as plt
plt.bar(df.region, df.margin); plt.show()'
# → results: [{"omitted": {"png": 16044}, "chart": {"type": "bar", "elements": [...]}}]   # --images base64 to inline the png
gsk sb-box run-code -s "$ID" --code '1/0'
# → cellError = {"name": "ZeroDivisionError", "value": "division by zero", "traceback": "…"}; the kernel survives
#   (REST calls this field `error`)

curl -s -X POST "$API/sandboxes/$SBX/run-code" -H "$H" -H 'Content-Type: application/json' \
  -d '{"code": "df.margin.sum()", "timeout": 60}'
Field Meaning
code (required) the cell
language kernel language; default python (other kernels only if the template ships them)
contextId run in a specific interpreter context; default = the sandbox's default Python context
envVars extra environment for this cell
timeout the cell's budget in seconds (default 60, max 540); on expiry the reply carries error.name = "InterpreterTimeout"
images REST default base64 (png/jpeg/pdf inline); the CLI defaults to omit, which drops them and reports their sizes under results[].omitted

results[] is a list of MIME bundles exactly as the kernel displayed them: text, html, markdown, svg, png, jpeg, pdf, latex, json, javascript, plus two structured forms — data (a DataFrame as columns) and chart (a matplotlib figure decoded into type, axes and series). The interpreter ships in the public base template only: a template built from your own image, or a public-base sandbox predating 2026-10-13, answers 409 sb_box_interpreter_unavailable (installing ipykernel yourself does not help — use exec with a script). 502 sb_box_interpreter_start_failed carries the start log.

The interpreter also answers directly on port 49999 (https://49999-<id>.sandbox.…/execute), under the sandbox's normal public-port rules: on a default sandbox (allowPublicTraffic: true) anyone who knows the sandbox id can post code to it. If that is not acceptable, create the sandbox with allowPublicTraffic: false and send the traffic token (§10.1).


9. Filesystem

Files move by path; the body is the content.

# write (parents are created)
curl -s -X POST "$API/sandboxes/$SBX/files/stream?path=/home/user/data/in.csv" -H "$H" --data-binary @in.csv
# → {"written":20}
# read
curl -s "$API/sandboxes/$SBX/files/stream?path=/home/user/out.json" -H "$H" -o out.json
# list
curl -s "$API/sandboxes/$SBX/files/list?path=/home/user" -H "$H"
# → [{"name":"data","path":"/home/user/data","isDir":true,"size":4096},
#    {"name":"in.txt","path":"/home/user/in.txt","isDir":false,"size":20}, …]

CLI: write --path … --content …, read --path …, ls --path … (text content; the CLI decodes as UTF-8 and replaces undecodable bytes). Binary round-trips are byte-exact over REST.

Query Meaning
path Absolute, or relative to the user's home (path=rel.txt → /home/user/rel.txt). Required for write and read; files/list defaults to /.
user Omit for the image's default user (user), or root.
  • A root-only file answers 403 without user=root; a missing path answers 404.
  • The guest needs GNU coreutils (any Debian/Ubuntu image); Alpine-only images are not supported for file operations.

Every sandbox gets a 20 GB root disk, billed running or frozen with no free allowance (billing §1, §2).


10. Networking

10.1 Public URL per port

Anything listening on a port inside the sandbox is reachable at

https://<port>-<sandbox-id>.sandbox.<region>-prod.sspark.ai

Read the host from the sandbox record rather than building it: portUrl is this URL with a literal {port} placeholder, domain the suffix after <port>-<sandbox-id>. (the suffix differs per region).

curl -s -X POST "$API/sandboxes/$SBX/commands/start" -H "$H" -H 'Content-Type: application/json' \
  -d '{"command": ["python3", "-m", "http.server", "8000", "--directory", "/home/user"]}'
PORT_URL=$(curl -s "$API/sandboxes/$SBX" -H "$H" | jq -r .portUrl)   # https://{port}-sbx-….sandbox.westus2-prod.sspark.ai
curl -s "${PORT_URL/\{port\}/8000}/data/in.txt"
# → hello from the guide

A request to a frozen sandbox resumes it first (a second or two), so a preview server or webhook receiver costs nothing while idle.

Two network options on create shape these URLs:

  • allowPublicTraffic: false gates every port URL of the sandbox behind a token. The create response then carries trafficAccessToken; send it as the X-Sb-Traffic-Token header. Without it a request answers 401.

    curl -s -X POST "$API/sandboxes" -H "$H" -H 'Content-Type: application/json' \
      -d '{"templateId": "tpl-7b7c…", "timeout": 600, "network": {"allowPublicTraffic": false}}'
    # → {"id": "sbx-0b3e…", …, "trafficAccessToken": "6c5bfcbd-…"}
    curl -s -o /dev/null -w '%{http_code}\n' https://8000-sbx-0b3e….sandbox.westus2-prod.sspark.ai/         # 401
    curl -s -o /dev/null -w '%{http_code}\n' -H 'X-Sb-Traffic-Token: 6c5bfcbd-…' https://8000-sbx-0b3e….sandbox.westus2-prod.sspark.ai/   # 200
    
  • maskRequestHost rewrites the Host header your service sees, for apps that validate it; ${PORT} is substituted ("app-${PORT}.internal" → a server on 8000 sees Host: app-8000.internal).

10.2 Internet access and egress rules

Outbound internet is on by default. Restrict it per sandbox with the network block on create:

curl -s -X POST "$API/sandboxes" -H "$H" -H 'Content-Type: application/json' -d '{
  "templateId": "tpl-7b7c…", "timeout": 0,
  "network": {
    "allowInternetAccess": false,             # default true
    "allowOut": ["10.20.0.0/16", "1.1.1.1/32"], # IPs / CIDRs that stay reachable
    "denyOut": []                              # IPs / CIDRs to block while internet is on
  }
}'
network. field Type · default Meaning
allowInternetAccess bool · true false blocks all outbound traffic except allowOut.
allowOut array of IPs / CIDRs · [] Destinations that stay reachable when internet access is off.
denyOut array of IPs / CIDRs · [] Destinations blocked while internet access is on.
allowPublicTraffic bool · true false puts every port URL behind a token (10.1).
maskRequestHost string · none Host header your service sees on port-URL requests; ${PORT} is substituted (10.1).

Rules are IPs and CIDRs only; hostnames are rejected at create:

400 {"detail": "invalid network policy: allowOut: not an IP or CIDR (domains not yet supported): \"api.example.com\""}

The policy is fixed at create time. The CLI does not expose network; use REST.


11. Sandbox metrics

curl -s "$API/sandboxes/$SBX/metrics" -H "$H"
# → [{"timestamp":"2026-09-21T11:32:33Z","timestampUnix":1789990353,"cpuCount":2,"cpuUsedPct":0,
#     "memUsed":98181120,"memTotal":4196507648,"memCache":75390976,
#     "diskUsed":2837368832,"diskTotal":20957446144}, …]

One sample every 5 s while the guest runs; bytes for memory and disk, percent of all cores for CPU. A frozen sandbox answers with the samples recorded while it ran; a killed one answers 409.

Query Meaning
start, end Unix seconds (integers). Default window: the last hour up to now. At most 1,200 samples are returned.

12. Sharing

Grant another Genspark user access to a sandbox or a template. read lets them see it (and, for a template, create from it); write lets them operate a sandbox (exec, files, freeze/resume, clone). Kill and reset stay owner-only.

curl -s -X PUT "$API/sandboxes/$SBX/grants/$USER_ID" -H "$H" -H 'Content-Type: application/json' \
  -d '{"perm": "read", "expires_at": "2026-12-31T00:00:00Z"}'
# → {"sandbox_id":"sbx-…","grantee_id":"…","permission":"read","granted_at":"…","expires_at":"2026-12-31T00:00:00.000000+00:00"}
curl -s "$API/sandboxes/$SBX/grants" -H "$H"                     # list
curl -s -X DELETE "$API/sandboxes/$SBX/grants/$USER_ID" -H "$H"  # → {"status":"revoked"}

Templates: the same three calls under /templates/{id}/grants/{userId}.

Body Meaning
perm read or write (default read); anything else is 400 invalid permission
expires_at Optional RFC 3339 timestamp such as 2026-12-31T00:00:00Z, or a date YYYY-MM-DD (= midnight UTC); the grant lapses then.
reason Optional free text for your own records; not returned

{userId} is the other user's Genspark id. Grantees list what is shared with them via GET /sandboxes/shared and GET /templates/shared (no console tab). Console for granting: sandbox or template page → Sharing / Grants.


13. Templates

A template is a booted, snapshotted environment. Put everything slow — apt, pip, model downloads, warm caches — into the template, not into each run.

13.1 Build

gsk sb-box build-template --template py312 --image python:3.12-slim \
  --cpu_count 2 --memory_mb 2048 --disk_size_gb 20 \
  --start_cmd 'pip install -q numpy pandas && touch /tmp/ready && sleep infinity' \
  --ready_cmd 'test -f /tmp/ready'
# → {"templateId": "tpl-…", "status": "building", "region": "westus2", "owner": "sbbox/<your user id>"}
curl -s -X POST "$API/templates/build" -H "$H" -H 'Content-Type: application/json' -d '{
  "name": "py312",
  "image": "python:3.12-slim",
  "cpuCount": 2, "memoryMB": 2048, "diskSizeGB": 20,
  "startCmd": "pip install -q numpy pandas && touch /tmp/ready && sleep infinity",
  "readyCmd": "test -f /tmp/ready",
  "description": "Python 3.12 with numpy and pandas"
}'
Field REST gsk Notes
name name --template shown in lists; what templateName looks up
source image or dockerfile --image a public container image, or a Dockerfile as a string (REST only)
CPU cpuCount (default 1) --cpu_count (1) up to your plan's cap
memory memoryMB (default 2048) --memory_mb (2048) up to your plan's cap; the public base itself is 2 vCPU / 4 GB
disk diskSizeGB (default = your plan's cap) --disk_size_gb (20) 20 GB is the cap on every plan today
start command startCmd --start_cmd must end in a long-running process (default sleep infinity)
readiness readyCmd --ready_cmd polled up to 60 s; exit 0 = ready to snapshot
region region --region which region builds and hosts it (see 13.4); omit for the default
description description — free text shown in the console
visibility namespace --namespace omit = private (share it with a grant). The public catalog is published by Genspark.

Poll until status is ready:

gsk sb-box templates | jq '.data.templates[] | select(.name=="py312") | .status'
curl -s "$API/templates/$TPL" -H "$H"
# → {"id":"tpl-…","name":"py312","namespace":"…","base_image":"python:3.12-slim","region":"westus2",
#    "status":"ready","is_owner":true,"can_create":true,"created_at":"…","updated_at":"…"}

The template record:

Field Meaning
id tpl-…; use it as templateId
name what templateName resolves; several templates may share a name (the newest ready one wins)
description your free text
namespace public for the shared catalog; otherwise the owner's account id (a private template)
base_image the image it was built from, or dockerfile
status building → ready, or error when the build failed
region where it was built and where its sandboxes run
is_owner, can_create whether you own it, and whether you may create sandboxes from it right now (false while building)
cogen_id the owner's account id (shown on your own and on shared templates)
created_at, updated_at timestamps

A build takes about 5–7 minutes for a small image, longer when startCmd installs packages. Up to 5 builds run at once per account (billing §2). A failed build reports status: error with the reason in buildError (§15.2); the usual causes are a wrong image name, a startCmd that exits, or a readyCmd that cannot pass within 60 s.

13.2 How the build works: start and ready commands

The build boots the image, runs startCmd, polls readyCmd until it exits 0 (up to 60 s), then snapshots memory and disk. Whatever startCmd left running is already running when a sandbox comes up.

Two rules:

  • End startCmd with a marker and probe the marker. A probe like python3 -c "import pandas" can pass while pip is still running, and the snapshot then captures a half-finished install. … && touch /tmp/ready && sleep infinity with readyCmd test -f /tmp/ready cannot.
  • Probe what sandboxes will actually use — a port listening, a virtualenv importing.

startCmd runs once, at build time. It does not re-run when a sandbox is created; use exec or a background command for per-sandbox setup.

13.3 Names, rebuilds and the public catalog

  • templateName resolves to the newest ready template with that name you can see, so a rebuild under the same name rolls forward without changing callers. base is an alias for the newest ready public base.
  • Your templates are private by default; share one with a grant (§12). The public catalog (namespace: public, names like sb-box-base-<sha>-<date>) is published by Genspark and usable by everyone.
  • DELETE /templates/{id} (gsk sb-box delete-template --template tpl-…) removes a template you own; sandboxes already created from it keep running. Afterwards GET /templates/{id} answers 404 template not found.

13.4 Regions

Templates are region-scoped: create always lands on the template's region. GET /regions lists the regions and the default:

{"regions":["default","westus2"],"default":"westus2"}

14. gsk sb-box CLI reference

gsk sb-box <action> [--flag value …]. Every action is a thin call to the REST API, made as you.

Action Required Optional REST · what you get back
templates — — GET /templates · {"templates": […]}
build-template --image --template (name; default = the image tag), --cpu_count (1), --memory_mb (2048), --disk_size_gb (20), --start_cmd, --ready_cmd, --region, --log_paths POST /templates/build · {"templateId","status":"building","region","owner"}
delete-template --template tpl-… — DELETE /templates/{id}
create — --template (name, base, or tpl-… id; required), --timeout (seconds; default none; frozen at the deadline), --session POST /sandboxes · the sandbox record
list — — GET /sandboxes · {"sandboxes": […], "hasMore": false}
get -s — GET /sandboxes/{id} · the sandbox record
exec -s, -c --timeout (seconds; default 30, max 600) POST /sandboxes/{id}/exec (runs /bin/sh -c <command>) · {"stdout","stderr","exitCode"}
run-code -s, --code --language (python), --context_id, --timeout (seconds; default 60, max 540), --images (omit · base64) POST /sandboxes/{id}/run-code · {"stdout","stderr","results":[…],"cellError","executionCount"} (REST: error) — a cell on the sandbox's stateful interpreter (§8.5); the first call starts it
write -s, -p, -d — POST /sandboxes/{id}/files/stream · {"written","path"}
read -s, -p — GET /sandboxes/{id}/files/stream · {"content","path"}
ls -s -p (default /) GET /sandboxes/{id}/files/list · the listing
freeze -s — POST /sandboxes/{id}/freeze · the frozen record ("state":"frozen"); only a freeze that outruns ~50 s answers freezing — then poll get
resume -s — POST /sandboxes/{id}/resume · the sandbox record
versions -s — GET /sandboxes/{id} → {"sandbox_id","state","versions":[{version,frozenAt,label}]}
clone -s --version (omit = the live state) POST /sandboxes/{id}/clone · the new sandbox's record
rollback -s, --version (0 = last committed freeze) — POST /sandboxes/{id}/rollback · the sandbox record
timeout -s, --timeout (seconds from now) — POST /sandboxes/{id}/timeout · {"status":"ok"}
kill -s — DELETE /sandboxes/{id} · {"status":"killed","sandbox_id"}
describe — — prints the built-in usage guide

Flags:

Flag Actions Meaning
-s, --sandbox_id get, kill, exec, run-code, write, read, ls, freeze, resume, rollback, clone, versions, timeout the sandbox id (sbx-…)
--session the same actions, plus create a session name in place of --sandbox_id: the CLI remembers which sandbox the name owns and reuses it; run-code / exec / write create one when there is none (§14.1)
-t, --template create, build-template, delete-template create: template name or tpl-… id (default: the newest ready public base). build-template: the new template's name (default: the image tag). delete-template: the tpl-… id
-c, --command exec shell command line, run as /bin/sh -c <command>
--code run-code the cell's source
--language, --context_id, --images run-code kernel language (default python); interpreter context id; omit (default) or base64 for png/jpeg/pdf results
-p, --path write, read, ls file path, absolute or relative to /home/user; ls defaults to /
-d, --content write file content (text)
--timeout create, timeout, exec, run-code create: lifetime in seconds (default 0 = no deadline; a deadline freezes the sandbox). timeout: new lifetime in seconds from now. exec: command timeout in seconds (default 30, max 600). run-code: the cell's budget in seconds (default 60, max 540)
--version rollback, clone rollback: 0 = the last committed freeze (default), N = archived version N. clone: fork archived version N; omit to fork the live state
--image build-template container image to build from (required)
--cpu_count, --memory_mb, --disk_size_gb build-template sandbox size: vCPUs (default 1), memory in MB (2048), disk in GB (20); capped by your plan
--start_cmd, --ready_cmd build-template the start and readiness commands (§13.2); --start_cmd defaults to sleep infinity
--region build-template region to build in; omit for the default. create always lands on the template's region
--log_paths build-template comma-separated absolute file globs inside the guest to collect (max 8)
--args-file any path to a JSON object (- = stdin) with server parameters; file keys override flags

--args-file carries parameters the shell would mangle ($, quotes, newlines):

cat > req.json <<'JSON'
{"sandbox_id": "sbx-…", "path": "/home/user/run.sh", "content": "echo \"$HOME\"\ncd /tmp\n"}
JSON
gsk sb-box write --args-file req.json

Not reachable from the CLI (use REST): metadata, network and lifecycle.onTimeout on create; timeout on resume / rollback; reset, metrics, streaming exec, background commands, PTY, sharing grants, GET /usage, GET /limits.

The envelope. Every CLI call prints exactly one JSON object:

Field Meaning
version envelope format version (1)
status ok or error — check this, not the process exit code
message success, or the reason: the API's HTTP status and URL, or a local validation message
data the API response as documented in this guide (null on error)
session_state unused for sb-box; always null

An API refusal:

{"version": 1, "status": "error",
 "message": "Client error '404 Not Found' for url '…/api/sb-box/v1/sandboxes/sbx-doesnotexist'",
 "data": null}

A local error (e.g. no ready public base template to create from; pass --template <name|tpl-id>) uses the same envelope.

14.1 Sessions: --session instead of a sandbox id

--session <name> keeps every command of one task on the same sandbox without carrying an id:

gsk sb-box run-code --session q3-report --code 'import pandas as pd; print(pd.__version__)'
# → data = {…, "session": {"key": "q3-report", "sandbox_id": "sbx-4321f1…", "created": true}}
gsk sb-box write    --session q3-report --path /home/user/data.csv --content "$(cat data.csv)"
gsk sb-box run-code --session q3-report --code 'df = pd.read_csv("/home/user/data.csv"); df.describe()'
gsk sb-box kill     --session q3-report         # also drops the binding
  • The first run-code, exec or write on a name creates a sandbox (from the newest public base template, or --template <name|tpl-id> if given; no hard deadline) and binds the name to it. Later commands reuse it (a frozen one is resumed). A sandbox that was killed or hit a deadline is replaced on the next creating command.
  • get, read, ls, kill, freeze, resume, … only use an existing binding and fail with a clear message when there is none — they never create. create --session binds the new sandbox explicitly.
  • An explicit --sandbox_id always wins and leaves the binding alone.
  • Without the flag the name comes from the environment: GSK_SB_BOX_SESSION if set, else the session id a host agent exports (CLAUDE_CODE_SESSION_ID inside Claude Code), so a bare gsk sb-box run-code --code … in Claude Code runs on that conversation's sandbox. With no name anywhere: sandbox_id required.
  • Every successful call echoes the binding as data.session (key, sandbox_id, created). Bindings live in ~/.genspark-tool-cli/sb-box-sessions.json, scoped by API host, on the machine that runs gsk — a different machine is a different session.

14.2 Use SB-Box from a coding agent

A coding agent with gsk on its PATH can use SB-Box through one skill file. Install it into a project:

curl -fsSL https://www.genspark.ai/sb-box/opencode/install.sh | sh
# wrote  .opencode/skills/sb-box/SKILL.md

SKILL.md is an agent skill the model loads on demand. It tells the model to name each task's sandbox with --session (§14.1), copy files in with write, use run-code (§8.5) for analysis and exec for scripts, read results back and kill when done. Works with any agent that reads SKILL.md skills; opencode looks in .opencode/skills/. Inside Claude Code --session is optional (§14.1).

Requirements: npm i -g @genspark/cli && gsk login. The install script never overwrites existing files. The intro page shows a recorded session.


15. REST API reference

Beta. The REST API is in beta: the routes below are supported, but response fields may still be added and the rough edges noted in §19 are being worked on. The CLI and console are the stable surfaces.

Base URL https://www.genspark.ai/api/sb-box/v1. The former /api/sandbox/v1 prefix still answers, with Deprecation and Link: <…>; rel="successor-version" headers. Every request needs the key (§2): X-Api-Key: gsk-… or Authorization: Bearer gsk-…. Without a key HTTP calls get a 401 JSON body and …/ws/pty refuses the handshake. Request bodies are JSON (Content-Type: application/json) unless a section says otherwise; every lifetime is in seconds.

Conventions used below:

  • Auth — who may call it: any (your key), write (the owner or a user holding a write grant on the sandbox), owner (only the creator).
  • Required — a parameter without a default; the call answers 400 without it.
  • Errors are JSON, in one of two shapes — {"detail": …} (a string for ordinary errors, an object for the plan and billing refusals) or {"error": …} on the command routes (§16 maps routes to shapes). Each endpoint lists its codes beyond the common set.
  • Common to every endpoint: 401 authentication required (no key), 403 SB-Box is not enabled for this account (access not yet granted), 404 sandbox not found / 404 template not found (unknown id, or not yours and not shared — private resources never answer 403), 422 (a query parameter missing or of the wrong type — the body lists each offending parameter under errors[]), 500 / 503 (platform error, region unavailable — retry). Platform refusals pass through unchanged as detail.

Examples use $SBX, $TPL, $USER for ids and shorten timestamps.

15.1 Regions, limits, usage

GET /regions — list regions

The regions and the default for new templates. No parameters.

{"regions": ["default", "westus2"], "default": "westus2"}
Code Meaning
200 the list

GET /limits — your plan and standing

Your plan, its caps and current usage (billing §2). No parameters.

{"plan": "plus",
 "limits": {"maxConcurrentSandboxes": 50, "maxCpu": 8, "maxMemoryMB": 8192, "maxDiskGB": 20, "maxConcurrentBuilds": 5},
 "usage": {"runningSandboxes": 3, "buildingTemplates": 0}}
Field Meaning
plan free · plus · pro · team_plus · enterprise
limits the caps of that plan
usage live counts against the concurrency caps
Code Meaning
200 the record

GET /usage — metered usage and credits

Per-UTC-day run time, vCPU-seconds, GB-seconds and the credits debited (billing §3).

Query Type · default Meaning
days integer 1–366 · 30 how many days back from today
{"days": 7, "from": "2026-09-15", "to": "2026-09-21",
 "rateCard": {"creditsPerCpuCoreHour": 25.0, "creditsPerGbMemHour": 8.0, "creditsPerGbDiskHour": 0.05, "diskBilled": true},
 "direct": {"runSec": 6950, "cpuCoreSec": 13900, "memGBSec": 16045, "diskGBSec": 5184000, "credits": 204.1833},
 "daily": [{"day": "20260921",
            "direct": {"run_sec": 6950.0, "cpu_core_sec": 13900.0, "mem_gb_sec": 16045.0, "disk_gb_sec": 5184000.0, "credits": 204.1833}}]}

direct is your own usage (the only bucket debited). Days with no usage are omitted; daily[] uses YYYYMMDD and snake_case.

Code Meaning
200 the report
422 days outside 1–366 or not an integer

15.2 Templates

The template record returned by the template endpoints is described in §13.1: id, name, description, namespace (public or the owner's account id), base_image, status (building · ready · error), buildError (why, when error), region, source, is_owner, can_create, cogen_id, created_at, updated_at.

POST /templates/build — build a template

Boots the image, runs startCmd, waits for readyCmd, snapshots the result. Asynchronous: poll GET /templates/{id} until status is ready. Auth: any.

Body Type · default Meaning
name string · the image tag shown in lists; what templateName looks up
image string a public container image (python:3.12-slim). One of image / dockerfile is required
dockerfile string a Dockerfile as text, built for you (alternative to image)
cpuCount integer · 1 vCPUs; ≤ your plan's cap (409 sb_box_spec_limit over it)
memoryMB integer · 2048 memory; ≤ your plan's cap
diskSizeGB integer · 20 disk; 20 is the only accepted value today (over it → 400)
startCmd string · sleep infinity runs at build time; must end in a long-running process
readyCmd string · none polled up to 60 s; exit 0 = ready to snapshot
region string · the default region which region builds and hosts it (§13.4)
logPaths array of strings up to 8 absolute file globs inside the guest to collect
description string free text
curl -s -X POST "$API/templates/build" -H "$H" -H 'Content-Type: application/json' -d '{
  "name": "py312", "image": "python:3.12-slim", "cpuCount": 2, "memoryMB": 2048, "diskSizeGB": 20,
  "startCmd": "pip install -q numpy pandas && touch /tmp/ready && sleep infinity",
  "readyCmd": "test -f /tmp/ready"}'
{"templateId": "tpl-875c…", "status": "building", "region": "westus2", "owner": "sbbox/<your user id>"}
Code Meaning
200 build started; poll the template
400 dockerfile or image is required; diskSizeGB N exceeds the M GB limit for user templates on this plan; cpuCount / memoryMB must be integers
409 {"code": "sb_box_spec_limit", …} cpu or memory over your cap; {"code": "sb_box_build_concurrency_limit", …} five builds already running

GET /templates — list templates you can create from

Your own templates plus the public catalog (shared-with-you ones are under /templates/shared). Auth: any.

{"templates": [
  {"id": "tpl-7b7c…", "name": "sb-box-base-<sha>-<date>", "namespace": "public", "base_image": "…/sb-box-template-base:…",
   "status": "ready", "region": "westus2", "source": "user", "is_owner": false, "can_create": true,
   "created_at": "2026-09-20T11:25:45+00:00", "updated_at": "2026-09-20T11:25:45+00:00"},
  {"id": "tpl-b84b…", "name": "py312", "namespace": "5f63…", "base_image": "python:3.12-slim", "status": "ready", …, "is_owner": true}]}
Code Meaning
200 the list (possibly empty)

GET /templates/shared — templates shared with you

Templates another user granted you. Auth: any. No parameters. Answers a JSON array of template records ([] when none).

Code Meaning
200 the array

GET /templates/{id} — one template

The record with live build status. Auth: any user who can see it.

{"id": "tpl-875c…", "name": "verify-df", "namespace": "5f63…", "base_image": "dockerfile", "status": "error",
 "buildError": "build dispatch failed: …", "region": "westus2", "source": "user", "is_owner": true, "can_create": false,
 "created_at": "…", "updated_at": "…", "cogen_id": "5f63…"}
Code Meaning
200 the record
404 template not found — unknown, or private and not shared with you

DELETE /templates/{id} — delete a template

Sandboxes already created from it keep running. Auth: owner.

{"status": "deleted"}
Code Meaning
200 deleted
404 not yours, or unknown

PUT /templates/{id}/grants/{userId} — share a template

Lets another user ({userId} = account id) see and create from it. Auth: owner.

Body Type · default Meaning
perm read | write · read read = see it and create from it; write additionally lets them operate it
expires_at RFC 3339 timestamp or YYYY-MM-DD · none when the grant lapses (a date = midnight UTC)
reason string for your records; not returned
{"template_id": "tpl-b84b…", "grantee_id": "0000…abcd", "permission": "read",
 "granted_at": "2026-09-21T11:36:26.946265+00:00", "expires_at": "2026-12-31T00:00:00.000000+00:00"}
Code Meaning
200 grant created or updated
400 invalid permission: <value>; invalid expires_at: …
404 not your template

GET /templates/{id}/grants — list a template's grants

Auth: owner. No parameters.

[{"grantee_id": "0000…abcd", "permission": "read", "granted_at": "…", "expires_at": null}]
Code Meaning
200 the array ([] when none)

DELETE /templates/{id}/grants/{userId} — revoke a grant

Auth: owner. No body.

{"status": "revoked"}
Code Meaning
200 revoked (also when there was nothing to revoke)

15.3 Sandboxes: lifecycle

The sandbox record and its state values are described in §3.3.

POST /sandboxes — create a sandbox

Restores a template snapshot and returns when it is running (about half a second). Auth: any; the creator is the owner.

Body Type · default Meaning
templateName string name of a template you can see; the newest ready one wins. One of templateName / templateId is required; templateName takes precedence
templateId string exact tpl-… id
templateNamespace string · omit restrict the name lookup: public = the shared catalog only; anything else → 400
timeout number (seconds) · none lifetime; omitted or 0 = no deadline. timeoutMs (milliseconds) is deprecated (Deprecation: true + Warning: 299 … headers)
lifecycle.onTimeout freeze | kill · freeze what happens at the deadline
metadata object of strings · {} your key/values, echoed on every read
envVars object of strings · {} sandbox-wide environment for every command (§7)
network.allowInternetAccess bool · true false blocks outbound traffic except allowOut
network.allowOut array of IPs/CIDRs · [] reachable destinations while internet is off
network.denyOut array of IPs/CIDRs · [] blocked destinations while internet is on
network.allowPublicTraffic bool · true false gates the port URLs behind a token (§10.1)
network.maskRequestHost string · none Host header your service sees on port-URL requests; ${PORT} is substituted
namespace string · omit a grouping label, stored as <your user id>/<label>; filter with GET /sandboxes?namespace=<label>
curl -s -X POST "$API/sandboxes" -H "$H" -H 'Content-Type: application/json' -d '{
  "templateName": "py312", "timeout": 900,
  "metadata": {"purpose": "docs", "userId": "u-123"},
  "network": {"allowPublicTraffic": false}}'
{"id": "sbx-44fb…", "templateId": "tpl-7b7c…", "state": "running", "region": "westus2",
 "portUrl": "https://{port}-sbx-44fb….sandbox.westus2-prod.sspark.ai", "domain": "sandbox.westus2-prod.sspark.ai",
 "createdAt": "2026-09-21T11:32:30Z", "startedAt": "2026-09-21T11:32:31.760511059Z", "timeoutAt": "2026-09-21T11:47:30Z",
 "cpuCount": 2, "memoryMB": 4096, "diskSizeGB": 20, "idleTimeout": 600,
 "metadata": {"purpose": "docs", "userId": "u-123", "cogenId": "5f63…"},
 "trafficAccessToken": "6c5bfcbd-…"}

trafficAccessToken appears only when allowPublicTraffic is false, and only here.

Code Meaning
200 running
400 templateId or templateName is required — pick one from GET /templates; template is not ready (status: building); invalid timeout: 'abc' (must be a number); invalid network policy: allowOut: not an IP or CIDR (domains not yet supported): "…"
402 {"code": "sb_box_insufficient_credits", "balance": "0.00", "message": "…"} (billing §1)
404 no ready template with name '…'; template not found
409 {"code": "sb_box_concurrency_limit" | "sb_box_spec_limit", …} (billing §2)
499 client disconnected — you hung up before provisioning finished; nothing was created
504 <phase> timed out; please retry — the template lookup exceeded its 20 s budget

Gates run in this order: template access → not ready → credits → plan caps.

GET /sandboxes — list your sandboxes

Compact rows for what you created, newest first. Auth: any.

Query Type · default Meaning
state comma-separated states · all keep only running, starting, freezing, frozen, exited, killed
namespace string only rows created with that namespace label (stored form <your user id>/<label>)
{"sandboxes": [{"id": "sbx-44fb…", "template_id": "tpl-7b7c…", "state": "running", "region": "westus2", "source": "user",
                "portUrl": "https://{port}-sbx-44fb….sandbox.westus2-prod.sspark.ai", "domain": "sandbox.westus2-prod.sspark.ai",
                "namespace": "ns_2eeb…", "cogen_id": "5f63…", "created_at": "2026-09-21T11:32:31.845822+00:00", "updated_at": "…"}],
 "hasMore": false}

state is unknown when the region could not be read. updated_at moves with each lifecycle transition, usually within seconds; for exact moments read startedAt / frozenAt on the full record. No pagination yet (hasMore is always false).

Code Meaning
200 the list (possibly empty)

GET /sandboxes/shared — sandboxes shared with you

Sandboxes another user granted you access to. Auth: any. No parameters; answers a JSON array of records ([] when none).

Code Meaning
200 the array

GET /sandboxes/{id} — one sandbox

The full record (§3.3), with versions[] and frozenAt once frozen, plus source, namespace, is_owner, has_write. Auth: any user who can see it.

{"id": "sbx-44fb…", "templateId": "tpl-7b7c…", "state": "frozen", "region": "westus2",
 "createdAt": "…", "startedAt": "…", "frozenAt": "2026-09-21T11:34:51Z", "timeoutAt": "…",
 "cpuCount": 2, "memoryMB": 4096, "diskSizeGB": 20, "idleTimeout": 600,
 "versions": [{"version": 1, "frozenAt": "2026-09-21T11:33:10.922641058Z", "label": "auto"},
              {"version": 2, "frozenAt": "2026-09-21T11:34:44.162637328Z", "label": "auto"}],
 "metadata": {"purpose": "docs", "cogenId": "5f63…"},
 "source": "user", "namespace": "ns_2eeb…", "is_owner": true, "has_write": true,
 "cogen_id": "5f63…", "template_id": "tpl-7b7c…", "created_at": "…", "updated_at": "…"}
Code Meaning
200 the record
404 sandbox not found — unknown, killed, or not yours and not shared

DELETE /sandboxes/{id} — kill a sandbox

Irreversible: the machine and its disk are gone. Auth: owner.

{"status": "killed"}
Code Meaning
200 killed
404 unknown, already killed, or not yours

POST /sandboxes/{id}/freeze — freeze (save point)

Snapshots memory and disk to storage, releases the machine, archives the replaced snapshot as a version (§4, §5). Compute stops billing while frozen; the disk keeps billing (billing §1). Any later call or port request resumes it. Auth: write. No body. Waits for the snapshot (a few seconds):

{"id": "sbx-3f2c…", "templateId": "tpl-3218…", "state": "frozen", "createdAt": "…", "startedAt": "…",
 "frozenAt": "2026-09-22T04:07:54Z", "timeoutAt": "…", "cpuCount": 2, "memoryMB": 4096, "diskSizeGB": 20,
 "versions": [{"version": 1, "frozenAt": "2026-09-22T04:07:54.657154572Z", "label": "auto"}], "idleTimeout": 600, "metadata": {…}}
Code Meaning
200 frozen — the record above; on an already-frozen sandbox {"state": "frozen", "status": "already frozen"}
202 {"state": "freezing", "status": "freeze in progress"} — only when the snapshot outran the platform's wait (about 50 s); poll GET until frozen
409 the sandbox is not running (is not running (state=…)), or the freeze was cancelled by a concurrent resume
500 freeze failed — the snapshot export failed

Each freeze appends to versions[] the save point it replaced: after the first freeze, version 1 is the starting state (its frozenAt is the creation time); after the second, version 2 is the first freeze's state. The newest save point is always version: 0 on /rollback and /clone.

POST /sandboxes/{id}/resume — resume

Brings a frozen sandbox back, processes and memory intact, in about a second. Idempotent on a running sandbox. Auth: write.

Body Type · default Meaning
timeout number (seconds) · none re-grant this lifetime from now (also applied when already running)
version integer deprecated on this route: a non-zero value turns the call into POST /rollback with that version
{"id": "sbx-44fb…", "templateId": "tpl-7b7c…", "state": "running", "startedAt": "2026-09-21T11:34:45Z",
 "frozenAt": "2026-09-21T11:34:44Z", "timeoutAt": "2026-09-21T11:44:47Z", "cpuCount": 2, "memoryMB": 4096, "diskSizeGB": 20,
 "idleTimeout": 600, "versions": […], "metadata": {…}}
Code Meaning
200 running (already running = unchanged record)
402 credits used up (billing §1)
409 {"code": "sb_box_concurrency_limit", …} — resuming would exceed your running cap
428 {"reason": "memory_expired", "recovery": ["reset"]} (top level, not under detail) — the memory snapshot was reclaimed after 14 days frozen (§4.1); reset restarts it from disk
410 disk_lost — the disk is gone too; recreate

POST /sandboxes/{id}/rollback — roll back to a save point

Puts the sandbox back at a save point and resumes it. The state being left is archived first, so a rollback is reversible. Needs a settled sandbox. Auth: write.

Body Type · default Meaning
version integer · 0 0 = the newest save point (the last freeze); N = archived version N from versions[] — the state that freeze N replaced (see /freeze)
timeout number (seconds) · none re-grant this lifetime from now
{"id": "sbx-44fb…", "state": "running", "startedAt": "2026-09-21T11:36:18.188944137Z", "frozenAt": "2026-09-21T11:33:10Z",
 "timeoutAt": "2026-09-21T11:46:17Z", "versions": [{"version": 1, …}, {"version": 2, …}, {"version": 3, …}, {"version": 4, …}], …}
Code Meaning
200 rolled back and running
400 version negative or not an integer
404 version N not found for sandbox "…" — no such archived version
409 rollback requires a settled snapshot state (frozen/failed/exited), sandbox "…" is running — freeze first
410 disk_lost — never frozen and the disk is gone
428 memory snapshot expired, as on resume

POST /sandboxes/{id}/clone — fork

A new sandbox from a save point of this one, memory included; the source is untouched. On a running source without version the live state is snapshotted first (§5.1). Auth: write on the source; the caller owns the fork.

Body Type · default Meaning
version integer · 0 0 = the live state (running source) or the newest save point (frozen source); N = archived version N (see /freeze)
metadata object of strings · {} the fork's own metadata
{"id": "sbx-6d4a…", "templateId": "sbx-44fb…-clone-v2", "state": "running",
 "createdAt": "2026-09-21T11:34:54Z", "startedAt": "2026-09-21T11:34:54.897942222Z", "timeoutAt": "2026-09-22T11:34:54Z",
 "memoryMB": 4096, "idleTimeout": 600,
 "metadata": {"clonedFrom": "sbx-44fb…", "clonedVersion": "2", "purpose": "try-the-migration", "cogenId": "5f63…"}}

A fork starts with a 24-hour deadline; change it with /timeout.

Code Meaning
200 the fork is running
400 invalid version: N (must be >= 0)
402 / 409 credits, or the running cap, as on create
404 version N not found for sandbox "…"
428 the source's memory snapshot expired
503 the fork is still starting (Retry-After: 2; the body carries the partial record)

POST /sandboxes/{id}/timeout — change the lifetime

Auth: write.

Body Type · default Meaning
timeout number (seconds) · 0 new lifetime from now; 0 (or an empty body) clears the deadline. timeoutMs deprecated
{"status": "ok"}
Code Meaning
200 applied; GET shows the new timeoutAt (absent after 0)
400 invalid timeout: 'soon' (must be a number)

POST /sandboxes/{id}/reset — hard reset

Kills every process, reboots the kernel, reattaches the disk as it last was; startCmd runs again. Treat it as a power loss — unflushed data is lost, so run sync first when the disk state matters, and allow a few seconds before the first command. Auth: owner. No body. Answers the running record.

Code Meaning
200 reset and running
404 not yours
409 reset requires a settled or running sandbox (mid-freeze → retry)
503 the sandbox's storage is temporarily unavailable (Retry-After) — retry

GET /sandboxes/{id}/metrics — resource samples

One sample every 5 s while the guest runs. Auth: any user who can see it.

Query Type · default Meaning
start, end integer unix seconds · last hour → now time window; at most 1,200 samples
[{"timestamp": "2026-09-21T11:32:33Z", "timestampUnix": 1789990353, "cpuCount": 2, "cpuUsedPct": 0.7,
  "memUsed": 100118528, "memTotal": 4196507648, "memCache": 81022976, "diskUsed": 2837368832, "diskTotal": 20957446144}]

cpuUsedPct is percent of all cores; memory and disk are bytes. Empty until the first sample lands.

Code Meaning
200 the samples (possibly [])
409 the sandbox is killed
422 start / end not integers

15.4 Sandboxes: commands

A frozen sandbox is resumed first (§4).

POST /sandboxes/{id}/exec — run a command and wait

Auth: write.

Body Type · default Meaning
command array of strings · required argv; use ["/bin/sh", "-c", "…"] for shell syntax
cwd string · the user's home working directory
envVars object of strings variables for this command (over the sandbox-wide ones)
user string · the image's user (user) root to run as root
timeout number (seconds) · 60 capped at 600; at the deadline the command's process gets SIGKILL, its children do not — sh -c "sleep 20" kills the sh, the sleep runs on. Use exec sleep 20 or the binary directly if it must die with the timeout
{"stdout": "out\n", "stderr": "err\n", "exitCode": 3}

Output is capped at 10 MiB per stream. A non-zero exitCode is a normal 200.

Code Meaning
200 the command finished (any exit code)
400 command must be a non-empty list
408 {"error": "exec timed out after Ns", "stage": "timeout", "killed": true}
409 the sandbox is killed / cannot run commands

POST /sandboxes/{id}/sse/exec — run a command, stream the output

Same body as /exec; the response is text/event-stream. Auth: write.

event: execId
data: {"execId":"gp-stream-1789990365519283415"}

id: 1
event: stdout
data: {"data":"one\n"}

id: 2
event: stderr
data: {"data":"two\n"}

event: exit
data: {"exitCode":2}

An error event ({"error": "…", "stage": "…"}) replaces exit when the command could not run or timed out ("stage": "timeout").

Code Meaning
200 the stream opened; the outcome is in the events
400 command must be a non-empty list

POST /sandboxes/{id}/commands/start — start a background command

Returns immediately; the process survives client disconnects until kill or the sandbox ends. Auth: write.

Body Type · default Meaning
command array of strings · required argv
cwd string · the user's home working directory
envVars object of strings variables for this process
user string root or omit
{"execId": "bg-1789990375798611452", "pid": 75}

execId is what input / close / wait take; pid is what kill and send-stdin take.

Code Meaning
200 started
400 command must be a non-empty list

GET /sandboxes/{id}/commands — list background commands

Processes started here that have not been waited on yet. Auth: write. No parameters.

[{"execId": "bg-1789990375798611452", "pid": 75, "sandboxId": "sbx-44fb…"}]
Code Meaning
200 the array

POST /sandboxes/{id}/commands/{execId}/input — write to stdin

Body Type Meaning
data string · required bytes to write ("y\n")
{"written": 2}
Code Meaning
200 written
404 unknown execId (already waited on / reaped)

POST /sandboxes/{id}/commands/{execId}/close — close stdin

Sends EOF. No body.

{"status": "ok"}
Code Meaning
200 closed
404 unknown execId

POST /sandboxes/{id}/commands/{execId}/wait — wait for exit

Blocks until the process exits and removes it from the list. No body.

{"exitCode": 0}
Code Meaning
200 exited
404 unknown execId

POST /sandboxes/{id}/commands/kill — signal a process

Body Type · default Meaning
pid integer · required the process
signal integer · 9 e.g. 15 for a graceful stop
{"status": "ok"}
Code Meaning
200 signal sent (the entry stays listed until you wait)
400 pid must be a positive integer
404 no such process

POST /sandboxes/{id}/commands/send-stdin — write to stdin by pid

Body Type Meaning
pid integer · required the process
data string bytes to write
{"status": "ok"}
Code Meaning
200 written
404 unknown pid

15.5 Sandboxes: files

Auth: write for all three. Paths are absolute, or relative to the user's home (/home/user). A frozen sandbox is resumed first.

POST /sandboxes/{id}/files/stream — write a file

The request body is the file (Content-Type: application/octet-stream or none). Parent directories are created.

Query Type · default Meaning
path string · required where to write
user string · the image's user root to write as root
curl -s -X POST "$API/sandboxes/$SBX/files/stream?path=/home/user/data/in.csv" -H "$H" --data-binary @in.csv
{"written": 20}
Code Meaning
200 written; written is the byte count (0 creates an empty file)
422 path missing

GET /sandboxes/{id}/files/stream — read a file

The response body is the file (application/octet-stream).

Query Type · default Meaning
path string · required the file
user string · the image's user root to read a root-only file
Code Meaning
200 the bytes (an empty body = an empty file)
400 path is a directory
403 permission denied for that user — retry with user=root. A file inside a directory the user cannot enter (/root/…) reads as 404 instead
404 file not found
422 path missing

GET /sandboxes/{id}/files/list — list a directory

Query Type · default Meaning
path string · / the directory
user string root or omit
[{"name": "data", "path": "/home/user/data", "isDir": true, "size": 4096},
 {"name": "in.txt", "path": "/home/user/in.txt", "isDir": false, "size": 20}]
Code Meaning
200 the entries ([] for an empty directory — and for a path that does not exist)

15.6 Sandboxes: interactive terminal (PTY)

Auth: write. The console's Shell tab is built on these.

POST /sandboxes/{id}/pty — open a terminal

Body Type · default Meaning
command array of strings · ["/bin/bash"] the shell
rows, cols integers · 24, 80 terminal size in characters
cwd string · the user's home working directory
envVars object of strings variables for this terminal, over the sandbox-wide ones from create (§7)

The shell also gets TERM=xterm-256color.

{"execId": "pty-1789990381393509857", "pid": 81}
Code Meaning
200 opened

POST /sandboxes/{id}/pty/{execId}/input — keystrokes

The body is raw bytes (include \n for Enter). Answers {"status": "ok"}.

Code Meaning
200 delivered
404 unknown execId

GET /sandboxes/{id}/pty/{execId}/connect — terminal output

Server-Sent Events: event: data / data: {"data": "…"} with the terminal bytes, escape sequences included. When the shell ends the stream closes with event: exit / data: {"reason": "eof", "exitCode": 3} — exitCode is the shell's status (absent only when the session was reaped before it exited).

Code Meaning
200 the stream opened
404 unknown execId

POST /sandboxes/{id}/pty/{execId}/resize — resize

Body Type Meaning
rows, cols integers · required new size in characters

Answers {"status": "ok"}.

POST /sandboxes/{id}/pty/{execId}/kill — end the session

No body. Answers {"status": "ok"}.

GET /sandboxes/{id}/ws/pty — WebSocket terminal

Both directions on one connection. Authenticate with ?api_key=gsk-… or the X-Api-Key header; without a key the handshake is refused (HTTP 403). Every frame is a JSON text message:

Direction Frame Meaning
you → server, first {"command": ["/bin/bash"], "cwd": "/home/user", "rows": 24, "cols": 80, "envVars": {"FOO": "bar"}} opens the terminal; every field optional (defaults shown, envVars empty). Must arrive within 10 s
you → server {"type": "stdin", "data": "ls\n"} keystrokes
you → server {"type": "resize", "rows": 40, "cols": 120} new size
you → server {"type": "kill"} end the session (the server answers exit and closes)
server → you {"type": "stdout", "data": "…", "seq": 1} terminal output, escape sequences included
server → you {"type": "exit", "exitCode": 0} the shell ended; the connection closes
server → you {"type": "error", "error": "…"} the request could not be served (e.g. a first frame that is not JSON)

Closing the connection without kill leaves the shell running; end it with POST /pty/{execId}/kill.

15.7 Sandboxes: sharing

Auth: owner. Same shapes as the template grants.

PUT /sandboxes/{id}/grants/{userId} — share a sandbox

Body Type · default Meaning
perm read | write · read read = see it; write = also exec, files, freeze / resume, clone
expires_at RFC 3339 timestamp or YYYY-MM-DD · none when the grant lapses
reason string for your records
{"sandbox_id": "sbx-44fb…", "grantee_id": "0000…abcd", "permission": "read",
 "granted_at": "2026-09-21T11:36:25.599604+00:00", "expires_at": null}
Code Meaning
200 granted
400 invalid permission: admin; cannot grant to self; invalid expires_at: …
404 not your sandbox

GET /sandboxes/{id}/grants — list grants

[{"grantee_id": "0000…abcd", "permission": "read", "granted_at": "…", "expires_at": null}]
Code Meaning
200 the array

DELETE /sandboxes/{id}/grants/{userId} — revoke

Answers {"status": "revoked"}.

Code Meaning
200 revoked

16. Response codes

Success

Code Returned by Meaning
200 OK almost everything, including create The body is the record or result. A non-zero exitCode from exec is still a 200.
202 Accepted POST /sandboxes/{id}/freeze Only when the snapshot outran the ~50 s wait: body {"state": "freezing"}; poll GET until frozen.
200 with {"state": "frozen", "status": "already frozen"} freeze on a sandbox that is already frozen Nothing to do; idempotent.
200 with the unchanged record resume on a running sandbox Idempotent no-op (a timeout in the body is still applied).
200 with [] metrics right after create No sample yet; the first arrives within about 5 s.

Deprecated inputs (timeoutMs) succeed but add Deprecation: true and a Warning: 299 … header.

Errors

Errors are JSON in one of two shapes, chosen by route:

Shape Routes
{"detail": …} — a string for ordinary errors, an object for the billing and plan refusals create, get, list, freeze, resume, timeout, clone, reset, metrics, templates, grants; and the sse/exec, commands / commands/start (and the rest of §8.3), files/* and pty routes
{"error": "…", …} — extra fields vary exec and run-code (refusals add sandbox_id, state, suggestion; a timeout adds stage: "timeout", killed, §15.4); rollback (adds reason, recovery, resumable)

On a record the platform killed, exec answers 409 {"error": "sandbox … is in state \"killed\" and cannot be proxied", "sandbox_id": …, "state": "killed", "suggestion": …}, files/list answers 409 {"detail": "sandbox … is in state \"killed\" and cannot be proxied"}, freeze answers 409 {"detail": "sandbox … is not running (state=killed)"}, rollback answers 410 {"error": …, "reason": "killed", "recovery": ["recreate"], "resumable": false} and reset answers 410 {"detail": …}.

Status detail When
400 templateId or templateName is required — pick one from GET /templates · invalid timeout: 'abc' (must be a number) · template is not ready (status: building) · command must be a non-empty list · invalid network policy: … · invalid permission: admin · diskSizeGB 25 exceeds the 20 GB limit for user templates on this plan bad input — fix the request; nothing was created
401 (port URL) — a request to a port URL of a sandbox created with allowPublicTraffic: false without the X-Sb-Traffic-Token header (§10.1)
401 authentication required no key and no session
402 {"code": "sb_box_insufficient_credits", "balance": "0.00", "message": "…"} credits used up — create / resume / clone (billing §1)
403 SB-Box is not enabled for this account your account does not have SB-Box access yet
403 {"code": "sb_box_plan_required", "plan": "free", "message": "…", "upgrade": "https://www.genspark.ai/pricing"} Free plan — create / run calls need a paid plan; reads, freeze, kill and delete stay open (billing §2)
404 sandbox not found · template not found · no ready template with name '…' unknown, killed, or not yours and not shared
409 {"code": "sb_box_concurrency_limit" | "sb_box_spec_limit" | "sb_box_build_concurrency_limit", "plan", "limit", "current" | "requested", "message"} a plan cap (billing §2)
409 rollback requires a settled snapshot state (frozen/failed/exited), sandbox "…" is running rollback on a running sandbox — freeze first
409 sandbox "…" is killed; … any call other than GET / kill on a record the platform killed (§3.0)
422 {"status": -2, "message": "Request parameter validation failed", "errors": [{"loc": ["query", "days"], "msg": "Input should be greater than or equal to 1", …}]} a query parameter of the wrong type or range (days outside 1–366, a non-integer start on metrics)
428 {"reason": "memory_expired", "recovery": ["reset"]} (top level, not under detail) resume of a sandbox whose memory snapshot was expired by the platform: reset revives it from disk
499 client disconnected you hung up before the sandbox was provisioned; nothing was created
500 / 503 string or Internal Server Error platform error / region unavailable — retry
504 <phase> timed out; please retry the pre-create template lookup exceeded its 20 s budget

17. Plans, limits and billing

Rates, per-plan limits, running out of credits and GET /limits / GET /usage: Plans, limits and billing (markdown: /sb-box/billing.md).


18. SB-Box SDK (coming soon)

An SB-Box SDK is coming soon. Until then the REST API (§15) and the gsk sb-box CLI (§14) are the supported ways to use SB-Box.


19. Known limitations

§15 is the contract. Fix rolling out = fixed, reaching each region with its next platform update.

  • List rows' updated_at does not move on your own freeze / resume yet (idle freezes and other platform-side transitions do) — fix rolling out.
  • reset is a power-loss reboot: unflushed file data is lost (§15.3 reset).
  • The list endpoint has no pagination and no metadata filter.

20. Data, regions and security

  • Where it runs. A sandbox runs in the region of its template; the public base template is in westus2 (US West). Templates and sandboxes never move between regions (§13.4). Everything a sandbox stores — its disk, its memory snapshots, its versions — stays in that region.
  • What is kept, for how long. A running or frozen sandbox keeps its disk and up to 5 versions (§5). A frozen sandbox's memory snapshot expires after 14 days untouched; the disk stays (§4.1). kill deletes the disk, memory snapshots and every version immediately and removes the record (§3.5); only a sandbox the platform killed stays listable as killed for a while (§3.4). Deleting a template deletes its image and snapshot.
  • Who can reach a sandbox. Only the account that created it and the users it explicitly granted access to (§12), with their own tokens. Genspark never reads your data in a sandbox.
  • Network defaults. A sandbox has internet access and public port URLs by default. Restrict egress with network.allowOut / denyOut / allowInternetAccess, and put a per-sandbox token in front of its port URLs with allowPublicTraffic: false (§10). Do not copy into a sandbox any long-lived credentials you would not put on a shared server.
  • Isolation. Every sandbox is its own virtual machine with its own kernel; sandboxes never share a kernel with each other or with the host.

21. Acceptable use, abuse reports and feedback

Use of SB-Box is governed by the Genspark Terms of Service and Acceptable Use Policy; sandboxes are not a place for cryptomining, scanning or attacking other systems, hosting content you do not have the rights to, or evading rate limits of other services. Abuse leads to the sandbox being killed and the account's SB-Box access being closed.

  • Report abuse coming from a *.sandbox.*.sspark.ai URL or an SB-Box sandbox: open a ticket in the Help Center at https://www.genspark.ai/helpcenter, or email [email protected], with the URL or sandbox id and the time.
  • Feedback and bugs: the same Help Center, or the feedback entry in the product. Include the sandbox id, the command and the time (UTC).