Processes, files and terminals
execute runs one command per request, up to 15 minutes. For everything else, open a gateway session and talk to the sandbox directly:
- start processes that run for hours and stream their output, reattaching after a disconnect;
- send input, resize terminals, and send signals;
- list, read, write, and delete files in the workspace;
- run interactive terminals (PTY).
Every client uses the same session: your own scripts, AI agents, and Quave ONE Solutions alike.
Open a session
curl -X POST "$API/sandbox/session" \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"accountId": "ACCOUNT_ID", "sandboxId": "SANDBOX_ID", "scopes": ["exec", "file"]}'
{
"success": true,
"endpoint": "https://sandbox-gateway.REGION_DOMAIN",
"token": "qsb1....",
"expiresAt": "2026-10-11T12:05:00.000Z"
}
| Scope | Grants |
|---|---|
exec | Start, inspect, and stream processes; signal and status imply it. |
file | List, read, write, and delete files under /workspace. |
pty | Start processes with a terminal and resize them. |
- Requires the
quave:write:dangerousscope and permission to manage the sandbox, likeexecute. An unknown scope answers400. - A paused sandbox is resumed first. While the resume is still running, the answer is
409withdata.retryable: trueanddata.retryAfterSeconds. - The token is short-lived (see
expiresAt). Open a new session before it expires; sessions are cheap. A process keeps running when its session expires. - Send the token to the gateway as
Authorization: Bearer <token>. Never send your Quave ONE token to the gateway. - Opening a session is audited (sandbox and scopes only). Output and file contents never pass through or get stored by Quave ONE.
In the examples below, GW is the session endpoint and SESSION its token.
Processes
Start a process
curl -X POST "$GW/v1/process/start" \
-H "Authorization: Bearer $SESSION" -H 'Content-Type: application/json' \
-H 'Idempotency-Key: build-42' \
-d '{
"config": {
"command": ["sh", "-c", "cd /workspace/persist/repo && npm ci && npm test"],
"cwd": "/workspace",
"envVars": {"CI": "1"}
},
"timeoutSeconds": 7200
}'
{ "executionId": "EXECUTION_ID", "processId": 42, "startedAt": "2026-10-11T12:00:00Z" }
commandis argv, never a shell string.timeoutSecondsis the process deadline. It defaults to 30 minutes; set it to the time the job needs, or-1for no deadline (the process then runs until it exits or the sandbox ends).Idempotency-Keyis required (up to 200 bytes). Use a key unique to the work you start: a retried start with the same key returns the same execution instead of starting a second process. Without it the gateway answers422 idempotency_key_required.- Add
"pty": {"cols": 120, "rows": 40}(needs theptyscope) to run the process in a terminal.
Check a process
GET $GW/v1/process/EXECUTION_ID returns state (running or exited), exitCode and reason once exited, startedAt, exitedAt, stdoutBytes, stderrBytes, and outputTruncated.
Stream output
curl -N "$GW/v1/process/EXECUTION_ID/stream?from=0" -H "Authorization: Bearer $SESSION"
The response is newline-delimited JSON (application/x-ndjson), one event per line, until the process exits:
{"seq":0,"init":{"processId":42}}
{"seq":1,"stdout":"aW5zdGFsbGluZy4uLgo="}
{"seq":2,"stderr":"d2FybmluZwo="}
{"seq":3,"exit":{"exitCode":0}}
stdoutandstderrare base64-encoded bytes.seqincreases by one per event. To reattach after a disconnect, stream again withfrom=the lastseqyou processed plus one. Events are replayed from a per-process buffer.- A
truncatedevent ({"reason":"ring_evicted"|"output_cap", "droppedBytes":...}) tells you that output was dropped: either you reattached after the buffer rolled over, or the process passed its total output cap (16 MB). The process keeps running. exit.reasonis set when the process did not exit on its own:deadline_exceeded,canceled,killed(out of memory or an unrequested kill), oragent_shutdown(the sandbox was paused or terminated).
Input, signals and terminals
| Request | Body | Effect |
|---|---|---|
POST /v1/process/EXECUTION_ID/stdin (?eof=true to close) | Raw bytes | Writes to the process's standard input. |
POST /v1/process/EXECUTION_ID/signal | {"signal": "SIGTERM"} (SIGINT, SIGTERM, SIGKILL) | Signals the process. |
POST /v1/process/EXECUTION_ID/resize | {"cols": 120, "rows": 40} | Resizes a PTY process. |
Keeping the sandbox alive
A sandbox is never terminated for idleness while a process started through the gateway is running, and API calls count as activity. The maximum lifetime (hardLifetimeSeconds) always applies. If you do not need a live stream, stdin or a terminal, the command API does the same with plain Public API calls.
Files
All paths resolve inside /workspace; anything outside answers an error (PATH_ESCAPE). Files you need after the sandbox ends belong under the persistent mount path (default /workspace/persist, see Persistence and volumes).
| Request | Effect |
|---|---|
GET /v1/files/list?path=/workspace/persist | Lists a directory: name, path, type, size, mode, modTime. |
GET /v1/files/stat?path=... | Metadata of one path. |
GET /v1/files/read?path=... | Raw file bytes (application/octet-stream). |
PUT /v1/files/write?path=...&mode=0o644 | Writes the raw request body to the file (creates or replaces). mode is optional. |
POST /v1/files/mkdir?path=...&parents=true | Creates a directory, with parents. |
DELETE /v1/files?path=...&recursive=true | Deletes a file or directory. |
curl -X PUT "$GW/v1/files/write?path=/workspace/persist/input.csv" \
-H "Authorization: Bearer $SESSION" --data-binary @input.csv
curl "$GW/v1/files/read?path=/workspace/persist/report.pdf" \
-H "Authorization: Bearer $SESSION" -o report.pdf
Errors
Gateway errors are JSON with a stable error code and a retryable flag. Retry retryable: true errors (for example sandbox_not_ready while the sandbox starts or resumes) after a short delay. 401 means the session token expired or is invalid: open a new session. 410 sandbox_expired means the sandbox reached its maximum lifetime: create a new sandbox and attach the same volume.