Quickstart
This walkthrough takes you from an empty account to a sandbox that ran a command, kept its files, and was cleaned up. Every step shows the Public API call and the equivalent MCP tool, so you can follow it from a script or let an AI client do it.
The API base URL is https://api.quave.cloud/api/public/v1, and every request sends your token in the Authorization header (no Bearer prefix). See Get Started for tokens.
export API=https://api.quave.cloud/api/public/v1
export TOKEN=YOUR_TOKEN
export ACCOUNT_ID=YOUR_ACCOUNT_ID
1. Pick an app and a region
Sandboxes belong to an app. Any app of the account works, including an empty one created only to group sandboxes (create-app over MCP, or the dashboard). Note its id as APP_ID.
Pick a region enabled for your account that has the Agent Sandbox add-on. If you are unsure, try the create call in step 4: a region without the add-on answers 409 and nothing is created.
2. Create a template
A template is the name agents ask for. Its runtime configuration lives in revisions (step 3).
curl -X POST "$API/sandbox-template/save" \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{
"accountId": "'"$ACCOUNT_ID"'",
"appId": "APP_ID",
"name": "python-workspace",
"description": "Python 3.12 with a persistent workspace"
}'
The response (201) carries template.templateId. MCP: save-sandbox-template.
3. Add a revision
A revision pins the image by digest and sets size, persistence, and timeouts. Get the digest of the image you want to run, for example with docker buildx imagetools inspect python:3.12-slim, and use the repo/image@sha256:... form.
curl -X POST "$API/sandbox-template/revision" \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{
"accountId": "'"$ACCOUNT_ID"'",
"templateId": "TEMPLATE_ID",
"imageDigest": "docker.io/library/python:3.12-slim@sha256:<64 hex characters>",
"imageRef": "python:3.12-slim",
"command": ["sleep", "infinity"],
"zClouds": 2,
"persistenceMode": "BOTH",
"snapshotExclude": ["node_modules", "*.log"],
"idleTimeoutSeconds": 1800,
"hardLifetimeSeconds": 14400
}'
commandkeeps the container alive so you can run commands in it. Without acommand, the image's own entrypoint must be long-running, or the sandbox exits and never becomesREADY.zClouds: 2is 1 vCPU and 1 GB of memory (1 zCloud = 0.5 vCPU + 512 MB).BOTHkeeps a live directory at/workspace/persistand snapshots the whole workspace when the sandbox is paused. See Persistence and volumes.- The sandbox is terminated after 30 minutes without activity or 4 hours in total, whichever comes first.
MCP: create-sandbox-template-revision. More options in Templates and images.
4. Create a sandbox
curl -X POST "$API/sandbox/create" \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{
"accountId": "'"$ACCOUNT_ID"'",
"appId": "APP_ID",
"templateId": "TEMPLATE_ID",
"workloadProfile": "QUAVE_INTERACTIVE_AGENT",
"ownerKind": "AGENT_CONVERSATION",
"ownerId": "conversation-42",
"region": "us-5",
"idempotencyKey": "conversation-42"
}'
The first call answers 201 with wasCreated: true and the sandbox, usually in ALLOCATING. Repeating the same call (same account, app, caller, and idempotencyKey) answers 200 with wasCreated: false and the same sandbox, so you can retry after any network error. MCP: create-sandbox.
Because the mode is BOTH and no volumeName was sent, Quave ONE created a shared volume named sbvol-<sandboxId> for it. The response shows it in volumeName.
Workload profiles
workloadProfile labels what the sandbox is for, and decides which ownerKind it must carry. Use the one that matches your system; ownerId is your own identifier for that owner.
workloadProfile | ownerKind | Use it for |
|---|---|---|
CODE_INTERPRETER_TURN | EXTERNAL_SESSION | Code execution on behalf of a session in your own product, such as a chat assistant's code interpreter. Put your session id in ownerId (and optionally externalSessionId). |
QUAVE_INTERACTIVE_AGENT | AGENT_CONVERSATION | An interactive agent conversation; one sandbox per conversation. |
QUAVE_AUTOMATION_RUN | AGENT_RUN | An automated, unattended agent run. |
A mismatched pair is refused. The profile does not change size, limits, or price.
5. Wait until it is ready
curl "$API/sandbox?accountId=$ACCOUNT_ID&sandboxId=SANDBOX_ID" -H "Authorization: $TOKEN"
Poll every few seconds until sandbox.status is READY. With room on the sandbox pool this takes a few seconds; when the pool has to add a node first, allow a few minutes. If the status becomes FAILED, read errorCode and errorRetryable (error codes). MCP: get-sandbox.
6. Run commands
curl -X POST "$API/sandbox/execute" \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{
"accountId": "'"$ACCOUNT_ID"'",
"sandboxId": "SANDBOX_ID",
"command": ["sh", "-c", "python3 -V && echo hello > /workspace/persist/hello.txt && ls -l /workspace/persist"],
"timeoutSeconds": 120
}'
The response is {"success": true, "result": {"exitCode": 0, "stdout": "..."}}. stdout and stderr are base64-encoded; decode them before reading. Empty streams are omitted. The command is an argv array; use ["sh", "-c", "..."] for shell features. Pass secrets in envVars; they exist only for that command. MCP: execute-sandbox-command. Details in Running commands.
7. Pause and resume
curl -X POST "$API/sandbox/pause" \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"accountId": "'"$ACCOUNT_ID"'", "sandboxId": "SANDBOX_ID"}'
Pausing snapshots the workspace (in SNAPSHOT and BOTH), releases the compute, and moves the sandbox to SUSPENDED. A paused sandbox is not billed for compute. MCP: pause-sandbox.
To continue, just run a command again: execute on a SUSPENDED sandbox resumes it (same sandboxId) and restores the snapshot. If the resume takes longer than the request, you get 409 with data.retryable: true and data.retryAfterSeconds; wait and repeat the same call. You can also call POST /sandbox/resume (MCP: resume-sandbox) and poll until READY.
8. Clean up
curl -X POST "$API/sandbox/terminate" \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"accountId": "'"$ACCOUNT_ID"'", "sandboxId": "SANDBOX_ID"}'
Termination is final and idempotent. It never deletes the shared volume: /workspace/persist is still there for the next sandbox (pass volumeName on create), and for inspection on the Shared Storage page. When you no longer need the data, remove the volume:
curl -X POST "$API/shared-volume/remove" \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"accountId": "'"$ACCOUNT_ID"'", "name": "sbvol-SANDBOX_ID", "purgeAfterDays": 7}'
The data stays restorable for purgeAfterDays days. MCP: terminate-sandbox, remove-shared-volume, restore-shared-volume.
Next steps
- Keep one workspace per conversation across many sandboxes: Persistence and volumes.
- Run builds and jobs longer than 15 minutes, move files, open terminals: Processes, files and terminals.
- Build your own image with your tools preinstalled: Templates and images.