Skip to content
Guides · Reference

Sandboxer API Error Codes, What They Mean and How to Fix Them

Updated October 27, 2026 .md

Short answer: Every Sandboxer API error is JSON with ok false, an HTTP status, a stable code, a message and a requestId. Branch on code. A 400 means fix the request, 401 or 403 the key or plan, 402 billing, 409 the sandbox's current state, 429 a limit, and 502 or 503 a machine or capacity problem worth retrying.

This page lists the codes the public API (https://sandboxer.com/api/v1) returns for sandboxes, commands, files, hosting and snapshots, grouped by area. We built the list from the API source and triggered the ones marked "seen on prod" against the live API on 2026-10-27. The limits and errors docs cover plan numbers and retry advice; the API reference covers every endpoint.

The error envelope#

Every error has the same shape. This is a real response for a sandbox id that doesn't exist:

curl -sS https://sandboxer.com/api/v1/sandboxes/sb_doesnotexist \
  -H "Authorization: Bearer $SANDBOXER_API_KEY"
{
  "ok": false,
  "type": "sandbox.error",
  "status": 404,
  "code": "not_found",
  "message": "Sandbox not found",
  "error": { "code": "not_found", "message": "Sandbox not found", "status": 404 },
  "requestId": "req_DaUDmVYzggPo88jwzp2D"
}
  • code is stable. Branch on it. message is for people and can change.
  • type names the area: sandbox.error, snapshot.error, account.error and so on.
  • error.details appears when there is more to say: the bad key, the state the sandbox was in, retryable, or the numbers behind a limit.
  • requestId (also the X-Request-Id header) is what support needs to trace a failure. Log it.

The sbx CLI reports the same code and exits non-zero (3 for unauthorized, 4 for not found, 1 for most others). Run from a script (output not a terminal), it prints the error as a JSON event:

sbx fork sb_x581f2kt1s
{"event":"error","error":"This sandbox was created with snapshots off and cannot be forked.","code":"snapshots_disabled","status":409,"requestId":"req_5HhAHp7T6Yj4vj2w76wm"}

Authentication and key scope#

Code Status What it means and the usual cause Fix
unauthorized 401 No key, or the key is wrong or revoked. Seen on prod. Send Authorization: Bearer $SANDBOXER_API_KEY with a live key.
api_key_expired 403 The key had an expiry date and it passed. Create a new key (API keys).
api_key_action_forbidden 403 The key's actions don't include this one, for example sandbox.delete or host. details.action names it. Use a key with that action, or widen the key.
api_key_sandbox_forbidden 403 The key is limited to certain sandboxes and this isn't one of them. Use a key that covers this sandbox.
api_key_environment_forbidden 403 The key doesn't cover the environment you named. Use a key scoped to that environment.
sandbox_private 403 A shared sandbox still holds its creator's logins. Ask the creator to share it fully.
owner_only 403 Only the sandbox's creator can do this (fork, delete, change its environment). Run it as the creator.
session_required 403 The endpoint takes a dashboard or CLI session, not an API key. Do it from the dashboard or sbx.
account_suspended, account_closed 403 The account can't act right now. A closed account can be reopened on the dashboard's Account page. Reopen it, or contact support with the requestId.

Real response with no key at all:

{"code":"unauthorized","error":{"code":"unauthorized","message":"Unauthorized","status":401},"message":"Unauthorized","ok":false,"requestId":"req_Zp2VPL3AanwQmY5PncaB","status":401,"type":"sandbox.error"}

Request validation (400, 413, 415)#

These never succeed on retry. Change the request.

Code Status What it means and the usual cause Fix
invalid_json 400 The body isn't valid JSON, or a required body is empty. details.reason has the parser's message. Seen on prod. Send a complete JSON object.
unsupported_media_type 415 A body was sent without a Bearer key and without Content-Type: application/json. Set the header.
invalid_type 400 type isn't small, default, large or xlarge. Seen on prod. Use one of the four sizes.
invalid_ttl 400 ttlSeconds is outside 1 to 2,592,000 (30 days). Seen on prod. Pass a value in range, or null.
invalid_env 400 A bad variable name (details.key), a reserved name, more than 100 variables or over 64 KB in total. Seen on prod. Use names like MY_VAR that don't start with a digit, and stay under the limits.
unknown_environment 400 No environment has the name you passed. Seen on prod. Check sbx env list.
invalid_setup_script 400 setupScript isn't a string, or is over 64 KB. Shorten it, or fetch the rest from inside the script.
invalid_idle_stop 400 idleStopMinutes is out of range. 0 means never; otherwise stay in the allowed range.
trial_auto_stop_required 400 On the free trial a sandbox must auto-stop, and the TTL was missing or too long. Pass a shorter ttlSeconds, or add a payment method.
command_required 400 POST /commands without command. Seen on prod. Send {"command": "..."}.
invalid_timeout 400 timeoutSeconds isn't a whole number from 1 to 600. Seen on prod. Use 600 or less, or run long work detached ("detached": true).
invalid_cwd 400 cwd is too long. Use a shorter path.
invalid_path 400 A file path that resolves outside /home/user or /tmp. Seen on prod. Read and write under those two directories.
invalid_encoding 400 File encoding isn't utf8 or base64. Use one of the two.
file_too_large 413 File reads and writes cap at 5 MiB, artifacts at 50 MiB. Use GET /sandboxes/{id}/artifacts, or sbx scp for bigger files.
invalid_port 400 Port missing or outside 1 to 65535. Seen on prod. Pass the port your server listens on.
invalid_name 400 A named snapshot name isn't 1 to 63 lowercase letters, digits or dashes, or is reserved. Seen on prod. Use something like web-stack.
invalid_labels 400 A label key has characters outside letters, digits, ., -, _, /. Fix the key.
invalid_cursor 400 A pagination cursor that wasn't returned by the API. Pass nextCursor as you got it.
invalid_idempotency_key 400 Idempotency-Key is longer than 255 characters. Use a UUID.
no_changes 400 PATCH /sandboxes/{id} with nothing to change. Pass at least one field.
type_too_small 400 A resume or fork asked for a size whose disk the sandbox's data won't fit. Moving to a smaller size isn't supported yet. Keep the size, or create a new sandbox of the size you want and copy what you need (resize guide).

Two of them from prod, trimmed to the inner error object:

{"code":"invalid_env","details":{"key":"1BAD"},"message":"Invalid environment variable name \"1BAD\".","status":400}
{"code":"invalid_timeout","message":"timeoutSeconds must be an integer between 1 and 600.","status":400}

Not found (404)#

Code Status What it means and the usual cause Fix
not_found 404 No sandbox with that id, it was deleted, or it belongs to another account. A mistyped path gives the same code with No route for GET /api/v1/.... Seen on prod. Check the id with sbx list and the path against the API reference.
file_not_found 404 Reading a file (GET /sandboxes/{id}/files?path=) or downloading an artifact (GET /sandboxes/{id}/artifacts?path=) at a path that doesn't exist. details.retryable is false. Seen on prod since 2026-10-27; before that, this returned a retryable 502 sandbox_direct_failed. Browsing a snapshot's files still returns not_found. Check the path, for example with sbx exec <id> "ls <dir>". Don't retry.
snapshot_not_found 404 No named snapshot with that name, a wrong or revoked share code, or no ready snapshot carrying all the fromLabels. Seen on prod. List them with GET /named-snapshots.

Another account's sandbox looks exactly like a missing one, on purpose.

Sandbox state conflicts (409)#

The request is fine but the sandbox isn't in a state that allows it. details.state often tells you which state it was in.

Code Status What it means and the usual cause Fix
sandbox_stopping 409 Resume sent while the stop is still writing the snapshot (archiving). Seen on prod. Wait for archived (sbx stop --wait), then resume. sbx resume retries this one for you.
machine_not_running 409 or 400 A command or file call on a stopped sandbox (409, details.retryable: false). Hosting a port or adding an SSH key on a stopped sandbox returns the same code with 400. Seen on prod, both. Resume first.
sandbox_starting 409 Still starting after the API waited 60 seconds for it. details.retryable: true. Retry in a few seconds.
sandbox_not_ready 409 Can't run commands, fork or save while it is in a state such as error, provisioning or archiving. Wait, resume, or POST /sandboxes/{id}/recover.
sandbox_not_resumable 409 Resume from a state that can't resume. Check state with GET /sandboxes/{id}.
sandbox_resuming 409 A resume is already running. Poll until ready.
snapshots_disabled 409 The sandbox was created with --no-snapshots, so it can't be resumed, forked or saved. Seen on prod. Create sandboxes you want to keep without that flag.
confirmation_required 409 DELETE without the X-Sandboxer-Confirm-Delete header set to the id. Seen on prod. Add the header, or use sbx delete <id> --yes.
subdomain_taken, name_taken 409 The subdomain or snapshot name already exists. Pick another one.
save_in_progress, snapshot_not_ready 409 A save under that name is still running. Wait for it to finish.
version_conflict 409 A versioned save with ifLatest that no longer matches. details.latest has the current number. Re-read and retry with the new number.
sandbox_in_cold_storage, snapshot_not_indexed 409 The sandbox's data is in cold storage. Resume it, then save or browse.
idempotency_in_progress 409 The first request with this Idempotency-Key is still running. Retry shortly with the same key.
idempotency_key_reused 409 Same Idempotency-Key, different body. Use a new key for a new request.
monitoring_off, monitoring_off_for_account, snapshots_off_for_account 409 The feature is turned off for this sandbox or account. Turn it on, or don't depend on it.
no_running_turn 409 Steer or interrupt with no agent turn running. Send a prompt instead.
card_check_pending 409 A newly added card is still being checked. Retry in a few seconds.

The stop-then-resume race, as it happened on prod (resume sent right after POST /stop returned 202):

{"code":"sandbox_stopping","message":"The sandbox is still stopping; resume it once it is archived.","status":409}

A command sent at the same moment:

{"code":"machine_not_running","details":{"retryable":false,"state":"archiving"},"message":"The sandbox is stopped. Resume it first.","status":409}

Billing, plan and limits (402, 403, 429)#

Code Status What it means and the usual cause Fix
payment_method_required 402 No card on file. details has checkoutUrl and trialEligible. Add a card on the dashboard's Billing page.
trial_expired 402 The 7-day trial ended. Pick a plan.
billing_required 402 Out of machine time, or an organization with no plan or credit. Add credit or upgrade.
storage_limit_reached 402 Stored data is over the plan's storage. details has usedBytes and includedBytes. Stop, resume and delete still work. Delete stopped sandboxes or named snapshots, or upgrade.
team_member_cap_reached 402 You hit the usage cap your organization set for you. Ask an org owner.
trial_machine_class_not_allowed 403 That size needs a payment method. Add a card, or use a smaller size.
machine_class_plan_required 403 The size needs a higher plan (xlarge). Upgrade, or use large.
limit_reached 429 Too many sandboxes active at once for your plan. details has limit and used. Stop or delete one. Retrying won't help.
member_limit_reached 429 Your organization caps how many you run at once. Stop one, or ask an org owner.
rate_limited 429 Too many starts in the minute or hour window, or too many requests overall (details.window: "requests"). Wait. Start limits send Retry-After and details.resetsAt.
daily_limit_reached 429 The rolling 24-hour start limit is used up. Wait until details.resetsAt.

Creates, resumes and forks each count as a start. Check how many you have left before a burst with sbx limits --json, as shown in the limits docs.

Machines and capacity (502, 503)#

Code Status What it means and the usual cause Fix
sandbox_direct_failed 502 The call reached the sandbox's machine but didn't complete. details.retryable says whether a retry is safe. Retry only when retryable is true. A command may already have run.
no_ready_machine 503 Only with failFast: true: no machine of that size was ready, so nothing was created and no start was counted. Retry, or fall back to another size.
capacity_exceeded 503 Not enough room for that size right now. details.available and details.availableBySize say how many fit. Try a smaller size, or again shortly.
http_500 500 An unexpected server error. Retry with backoff, and send the requestId to support if it repeats.

What to retry#

  • Retry with backoff: sandbox_starting, sandbox_stopping, idempotency_in_progress, card_check_pending, capacity_exceeded, no_ready_machine, http_500, and sandbox_direct_failed when details.retryable is true.
  • Wait, then retry: rate_limited and daily_limit_reached, until Retry-After or details.resetsAt.
  • Don't retry without changing something: every 400, 401, 402, 403, 404, limit_reached, snapshots_disabled.
  • Send an Idempotency-Key on POST /sandboxes so a retried create after a timeout returns the same sandbox instead of a second one.

FAQ#

Why do I get not_found and not sandbox_not_found?#

The API returns not_found for a missing, deleted or other account's sandbox, which is what we saw on prod. Older examples may show sandbox_not_found; branch on the 404 status plus not_found.

Is a 409 worth retrying?#

Some are. sandbox_starting, sandbox_stopping, idempotency_in_progress and card_check_pending clear on their own. The others, like snapshots_disabled or idempotency_key_reused, need a different request.

How do I avoid sandbox_stopping after a stop?#

Use sbx stop <id> --wait, or poll GET /sandboxes/{id} until state is archived, before you resume. The stop call returns while the memory snapshot is still being written.

What should I send support?#

The requestId from the error body or the X-Request-Id header, the code, and roughly when it happened.

More guides

Top AI sandbox platforms for code execution in 2026, ranked

For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.

Read the guide
Best sandboxes for running coding agents (Claude Code, Codex, Gemini CLI) in 2026, ranked

For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.

Read the guide
Best Daytona sandbox alternatives in 2026, ranked

The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.

Read the guide
Best E2B alternatives in 2026, ranked

If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.

Read the guide