Agent Sandboxes
Start an agent sandbox with one CLI command, verify by one text, then create, run commands, read/write files, and destroy sandboxes via the Sendblue API
For agents: Fresh sandbox setup starts with npx -y @sendblue/cli@latest sandbox init. API keys are created by that flow, and the verified phone is inferred from the SMS sender rather than typed into the terminal.
Every free_api and paid-plan Sendblue account can spin up sandboxes: isolated Linux machines in the cloud that your AI agent controls over HTTP. Run shell commands, install packages, clone repos, start dev servers — each sandbox is a real container with its own filesystem, isolated from every other account.
Free API accounts get $100 of sandbox compute free once a setup text verifies a real mobile sender. Paid plans get a larger allowance and more concurrent sandboxes.
Fresh sandbox setup
Section titled “Fresh sandbox setup”Fresh sandbox setup has no required user-provided inputs before the CLI runs. API keys are created by the flow, and the verified phone is inferred from the sender of the setup text rather than typed into the terminal.
Bootstrap command:
npx -y @sendblue/cli@latest sandbox initThe CLI starts the signup challenge, shows a Sendblue number plus an SB SETUP 123456 phrase, waits for the human to send that phrase, saves the new API keys locally, and creates the first sandbox.
The agent-facing step is to relay exactly what the CLI prints. The human chooses the phone implicitly by sending the text from that phone; the agent does not need to know or type the phone number.
If you need to print instructions and poll from a later tool call, keep the same HOME and use:
export SENDBLUE_HOME="${SENDBLUE_HOME:-${TMPDIR:-/tmp}/sendblue-sandbox-init}"mkdir -p "$SENDBLUE_HOME"export HOME="$SENDBLUE_HOME"
npx -y @sendblue/cli@latest sandbox init --no-wait
until npx -y @sendblue/cli@latest setup --check; do code=$? if [ "$code" -eq 3 ]; then sleep 5 else exit "$code" fidone
npx -y @sendblue/cli@latest sandbox createExit code 3 means the text has not arrived yet. After setup, npx -y @sendblue/cli@latest sandbox connect prints the ready-made prompt for driving sandboxes.
After setup: API endpoints
Section titled “After setup: API endpoints”POST /v3/sandboxes create a sandboxPOST /v3/sandboxes/{id}/exec run a shell commandPOST /v3/sandboxes/{id}/files write a fileGET /v3/sandboxes/{id}/files?path= read a fileGET /v3/sandboxes list sandboxes + usage + agent connect promptDELETE /v3/sandboxes/{id} destroy a sandboxSandboxes sleep automatically after 10 minutes idle and wake transparently on the next exec or file operation — you’re only billed while a sandbox is awake. Destroying a sandbox refunds the unused portion of its final awake window.
Sandboxes are ephemeral across sleep. Waking gives you the same sandbox id on a fresh filesystem — anything you wrote is gone. Treat every awake window as a complete unit of work: finish, then read your results out via the files API (or push them somewhere with curl) before going idle. Keeping a sandbox awake preserves state for as long as you keep working — any operation on that sandbox (exec or a file read/write) resets the idle timer. Listing sandboxes with GET /v3/sandboxes does not.
All raw HTTP endpoints use the standard API credentials saved by sandbox init:
sb-api-key-id: YOUR_API_KEY_IDsb-api-secret-key: YOUR_API_SECRET_KEYBoth values are available from the dashboard or sendblue show-keys. The same pair works for the whole Sendblue API. For a fresh account, these credentials are outputs of sendblue sandbox init, not prerequisites.
Pricing and the free tier
Section titled “Pricing and the free tier”| Plan | Compute allowance | Active sandboxes |
|---|---|---|
free_api | $100 (one-time) | 1 |
| Paid plans | $500 | 5 |
Compute is metered at $0.33 per awake-hour. A sandbox that runs a command and then sleeps 10 minutes later costs about $0.055. Sleeping sandboxes cost nothing.
Free-tier eligibility. The $100 free tier is tied to the phone that sends the setup text:
- The setup text must come from a standard mobile phone. That sender becomes the verified primary phone.
- Each phone number can claim the free tier once, ever, across all of Sendblue. A number that has already claimed on another account gets
403 sandbox_credit_claimed. - The number must be a standard mobile line. VoIP, toll-free, and other virtual/non-mobile numbers aren’t eligible for free credits (
403 sandbox_line_type_ineligible) — but work fine on paid plans.
Text verification details
Section titled “Text verification details”For a fresh free API account, the quickest path is sendblue sandbox init. The setup challenge comes before API keys exist. Relay the Sendblue number and exact phrase, and let the human text from whichever standard mobile phone they want to verify. The inbound text proves and binds that phone, creates the account, saves API keys, and creates the first sandbox.
# Keep signup credentials isolated from any existing Sendblue login on this machine.export SENDBLUE_HOME="${SENDBLUE_HOME:-${TMPDIR:-/tmp}/sendblue-sandbox-init}"mkdir -p "$SENDBLUE_HOME"export HOME="$SENDBLUE_HOME"
npx -y @sendblue/cli@latest sandbox initThe CLI requests a temporary Sendblue verification route and shows two values:
sharedNumber: the Sendblue number to text.challenge: the exact message body to send, for exampleSB SETUP 123456.
The human must send the challenge to sharedNumber from the phone they want to verify. If you render a convenience SMS link in an agent, build it from those response fields:
sms:<sharedNumber>?&body=<url-encoded challenge>The ?&body= form is deliberate, not a typo: iOS parses &body and Android parses ?body, and the combined form works on both — don’t “fix” it to ?body=. For example: sms:+15559876543?&body=SB%20SETUP%20123456. The SMS recipient is always sharedNumber; the verified phone is the sender of the text.
If you need to print instructions and poll later instead of waiting in one command, use --no-wait, then poll from the same HOME until the CLI succeeds:
export SENDBLUE_HOME="${SENDBLUE_HOME:-${TMPDIR:-/tmp}/sendblue-sandbox-init}"mkdir -p "$SENDBLUE_HOME"export HOME="$SENDBLUE_HOME"
npx -y @sendblue/cli@latest sandbox init --no-wait
until npx -y @sendblue/cli@latest setup --check; do code=$? if [ "$code" -eq 3 ]; then sleep 5 else exit "$code" fidone
npx -y @sendblue/cli@latest sandbox createFor recovery curls returned by sandbox errors, set the credential env vars from the printed values before running the curl. These guards fail locally until the agent has mapped API Key and API Secret from show-keys:
: "${SENDBLUE_API_KEY_ID:?set SENDBLUE_API_KEY_ID to the API Key from show-keys}": "${SENDBLUE_API_KEY_SECRET:?set SENDBLUE_API_KEY_SECRET to the API Secret from show-keys}"export SENDBLUE_API_KEY_ID SENDBLUE_API_KEY_SECRETRun the npx commands serially; repeated concurrent npx @sendblue/cli runs can collide in npm’s cache. The first inbound setup text verifies the primary phone for sandbox free-tier eligibility, so a fresh phone-onboarded account should not need a second verification text before its first sandbox request.
When your balance runs out you’ll get 402 balance_exhausted. You can still list and destroy your sandboxes; upgrade to a paid plan to keep computing.
Create a sandbox
Section titled “Create a sandbox”POST https://api.sendblue.com/v3/sandboxescurl -X POST 'https://api.sendblue.com/v3/sandboxes' \ -H 'sb-api-key-id: YOUR_API_KEY_ID' \ -H 'sb-api-secret-key: YOUR_API_SECRET_KEY'{ "data": { "id": "sbx_a9c7253d-8567-4fff-bf81-08a25076e50f", "object": "sandbox", "status": "running", "created_at": "2026-07-19T18:03:14.399Z" }}Returns 201 once the sandbox has actually booted (typically a few seconds) — the id is immediately usable for exec, no polling needed.
What’s inside
Section titled “What’s inside”Ubuntu 22.04 (x86_64), running as root, with full outbound internet access. Preinstalled: bash, git, curl, wget, Node 22 (node/npm/npx), bun, jq, unzip. Python is not preinstalled — install it in ~10 seconds when you need it:
{"command": "apt-get update -qq && apt-get install -y -qq python3 python3-pip", "timeout_ms": 120000}Anything else is an apt-get install -y away.
Run a command
Section titled “Run a command”POST https://api.sendblue.com/v3/sandboxes/{sandbox_id}/exec| Parameter | Type | Required | Description |
|---|---|---|---|
command | string | Yes | Shell command to run (max 8,192 chars) |
cwd | string | No | Working directory (max 1,024 chars) |
env | object | No | Extra environment variables (max 64; keys ≤128 chars, values ≤4,096) |
timeout_ms | number | No | 1,000–120,000. Default 60,000 |
curl -X POST 'https://api.sendblue.com/v3/sandboxes/sbx_YOUR_ID/exec' \ -H 'sb-api-key-id: YOUR_API_KEY_ID' \ -H 'sb-api-secret-key: YOUR_API_SECRET_KEY' \ -H 'Content-Type: application/json' \ -d '{"command": "git clone https://github.com/you/repo && cd repo && npm install && npm test", "timeout_ms": 120000}'{ "data": { "object": "sandbox.command", "sandbox_id": "sbx_...", "command": "git clone https://github.com/you/repo && cd repo && npm install && npm test", "success": true, "exit_code": 0, "stdout": "...test output...\n", "stderr": "", "duration_ms": 48210 }}Execs share one persistent shell while the sandbox is awake. cd, exported variables, and activated virtualenvs carry over between calls. That shell state resets along with the filesystem when the sandbox sleeps — don’t depend on it across awake windows. You can also pass cwd/env explicitly per call.
Waking is transparent. If the sandbox is asleep, the first request simply takes a few extra seconds while it wakes — no polling or retry needed.
Long-running work: commands are capped at 120 s. For anything longer, start a background process and poll it:
{"command": "nohup npm run build > /root/build.log 2>&1 & echo started"}then check on it with {"command": "tail -5 /root/build.log"}. Background processes keep running while the sandbox is awake (each exec or file read/write on the sandbox resets the 10-minute idle timer — polling the log this way also keeps it awake); when the sandbox sleeps, processes stop and the filesystem resets — collect results before going idle. On a 408 command_timeout the command is terminated, but work it completed first — files written, packages installed — remains on disk; check state before re-running.
stdout/stderr are capped at 262,144 characters (~256 KiB) each (stdout_truncated: true flags a cut). Commands that outlive timeout_ms return 408 command_timeout.
Read and write files
Section titled “Read and write files”POST https://api.sendblue.com/v3/sandboxes/{sandbox_id}/files # writeGET https://api.sendblue.com/v3/sandboxes/{sandbox_id}/files # readWrite (path + content, content up to 65,536 characters — fetch bigger payloads from inside the sandbox with exec + curl; parent directories are created automatically):
curl -X POST 'https://api.sendblue.com/v3/sandboxes/sbx_YOUR_ID/files' \ -H 'sb-api-key-id: YOUR_API_KEY_ID' \ -H 'sb-api-secret-key: YOUR_API_SECRET_KEY' \ -H 'Content-Type: application/json' \ -d '{"path": "/root/script.py", "content": "print(\"hello\")"}'{ "data": { "object": "sandbox.file", "sandbox_id": "sbx_...", "path": "/root/script.py", "size_bytes": 14 }}Read (files up to 1 MiB; URL-encode the path query param):
curl 'https://api.sendblue.com/v3/sandboxes/sbx_YOUR_ID/files?path=/root/script.py' \ -H 'sb-api-key-id: YOUR_API_KEY_ID' \ -H 'sb-api-secret-key: YOUR_API_SECRET_KEY'{ "data": { "object": "sandbox.file", "sandbox_id": "sbx_...", "path": "/root/script.py", "size_bytes": 14, "content": "print(\"hello\")" }}content is the raw UTF-8 text of the file. For binary files, base64 them inside the sandbox first (exec + base64 file.bin) and read that.
List sandboxes, check usage, connect your agent
Section titled “List sandboxes, check usage, connect your agent”GET https://api.sendblue.com/v3/sandboxes{ "data": { "object": "list", "sandboxes": [ { "id": "sbx_...", "object": "sandbox", "status": "sleeping", "created_at": "..." } ], "usage": { "object": "sandbox.usage", "plan": "free_api", "used_usd": 0.055, "cap_usd": 100, "remaining_usd": 99.945, "rate_usd_per_hour": 0.33, "max_active_sandboxes": 1 }, "connect": { "object": "sandbox.connect", "prompt": "You have access to a Sendblue sandbox: an isolated Linux container you control over HTTP. ..." } }}connect.prompt is the fastest way to put a sandbox in your agent’s hands: it’s a ready-made system-prompt snippet that teaches any AI agent the full sandbox API. Substitute your real keys for the YOUR_API_KEY_ID / YOUR_API_SECRET_KEY placeholders, paste it into your agent’s instructions, and the agent can drive the sandbox on its own. (If your account lives on a non-default Sendblue host, also swap the base URL in the prompt.)
Destroy a sandbox
Section titled “Destroy a sandbox”DELETE https://api.sendblue.com/v3/sandboxes/{sandbox_id}{ "data": { "id": "sbx_...", "object": "sandbox", "deleted": true } }Destroys the container and its disk immediately, and refunds the unused portion of the current awake window — destroy sandboxes you’re done with to conserve your balance. Listing and destroying always work, even if your free-tier eligibility lapses.
Errors
Section titled “Errors”Errors use the standard v3 envelope: { "error": { "code", "message", "request_id", "details?" } } — details carries specifics like used_usd/cap_usd on a 402 or active_sandboxes/max_active_sandboxes on a 409.
This table applies after an account is already using raw API requests. On a fresh machine with no Sendblue keys, npx -y @sendblue/cli@latest sandbox init is the setup path.
| Status | Code | Meaning | What your agent should do |
|---|---|---|---|
| 401 | unauthorized | Missing or invalid API credentials | Stop; verify both headers and key values with the account owner |
| 400 | invalid_request | Body/params failed validation | Fix the request per details; don’t retry as-is |
| 403 | sandbox_primary_phone_missing | Existing API-key account has no primary phone | Existing-account recovery only: if details.next_action is verify_primary_phone, ask the human for their real E.164 mobile number, set USER_MOBILE_E164, set SENDBLUE_API_KEY_ID / SENDBLUE_API_KEY_SECRET from show-keys or the current request headers, run details.verify_contact_curl, run details.line_lookup_curl, ask the human to text one returned Sendblue number from that phone, then retry |
| 403 | sandbox_access_denied | Plan doesn’t include sandboxes, or the primary number isn’t verified yet | Existing-account recovery only: if details.next_action is verify_primary_phone, set USER_MOBILE_E164 to details.primary_phone, set SENDBLUE_API_KEY_ID / SENDBLUE_API_KEY_SECRET from show-keys or the current request headers, run details.verify_contact_curl, run details.line_lookup_curl, ask the human to text one returned Sendblue number from that phone, then retry. Without that detail, stop and tell the human the plan is not eligible |
| 403 | sandbox_credit_claimed | This phone number already claimed free credits on another account | Stop and tell the human |
| 403 | sandbox_line_type_ineligible | VoIP/toll-free/virtual numbers can’t claim free credits | Stop and tell the human (paid plans aren’t screened) |
| 402 | balance_exhausted | Compute balance used up; you can still list and destroy sandboxes | Stop and tell the human to upgrade |
| 409 | sandbox_limit_reached | Too many active sandboxes for the plan | Destroy one, then retry |
| 404 | not_found | Unknown or already-destroyed sandbox id | Don’t retry; create a new sandbox if needed |
| 404 | file_not_found | Read of a nonexistent file | Check the path — use absolute paths, and remember the filesystem resets when the sandbox sleeps |
| 413 | file_too_large | File read over 1 MiB | Split or compress it inside the sandbox first |
| 408 | command_timeout | Command didn’t finish within timeout_ms | Raise timeout_ms (≤120000) or background the work; completed side effects persist |
| 429 | rate_limit_exceeded | Over 60 requests / 10 s | Back off briefly and retry |
| 429 | free_tier_paused | Free tier temporarily paused globally | Wait or tell the human to upgrade |
| 503 / 504 | service_unavailable / sandbox_timeout | Transient sandbox-service issue | Retry once after a short wait, then stop |
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Machine | 2 vCPU / 8 GiB RAM / 16 GB disk (x86_64 Linux) |
| Idle sleep | 10 minutes with no API calls to that sandbox (billing stops; filesystem resets on wake) |
| Command timeout | 120 s max per exec — run longer work in background processes |
| File write / read | 65,536 chars / 1 MiB via API (unlimited inside the sandbox) |
| Rate limit | 60 requests per 10 seconds |