Skip to content

Files and Processes

After you create or connect to a sandbox, use its data-plane URL and access token for file, directory, and streaming process operations.

Terminal window
export SANDBOX_URL="https://<route-token>.sandbox.watasuhost.com"
export SANDBOX_TOKEN="..."

Data-plane requests use:

Authorization: Bearer <sandbox access token>

You can also call the Watasu API under /v1/sandboxes/:id/... with your Watasu API key. The runtime data-plane API is the shorter path for active sandbox work.

Terminal window
curl "$SANDBOX_URL/runtime/v1/files?path=/workspace/hello.py" \
-X PUT \
-H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: text/plain" \
--data-binary 'print("hello from Watasu")'

For binary uploads, send the request body as bytes with an appropriate content type.

Use write_files when you already have several small file payloads to upload:

Terminal window
curl "$SANDBOX_URL/runtime/v1/files/write_files" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"files": [
{ "path": "/workspace/a.txt", "data_base64": "YWxwaGE=" },
{ "path": "/workspace/b.bin", "data_base64": "AAEC" }
]
}'

The response includes files, one metadata object per written path. SDKs expose this as filesystem.writeFiles(...), files.write_files(...), and Filesystem::write_files(...).

Terminal window
curl "$SANDBOX_URL/runtime/v1/files?path=/workspace/hello.py" \
-H "Authorization: Bearer $SANDBOX_TOKEN"

Use the control API when a browser, worker, or third-party service needs a short-lived file URL without a sandbox bearer token:

Terminal window
curl https://api.watasu.io/v1/sandboxes/123/files/upload_url \
-X POST \
-H "Authorization: Bearer $WATASU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "path": "/workspace/input.bin", "use_signature_expiration": 900 }'
curl https://api.watasu.io/v1/sandboxes/123/files/download_url \
-X POST \
-H "Authorization: Bearer $WATASU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "path": "/workspace/output.bin", "use_signature_expiration": 900 }'

The response includes file_url.url, method, path, and expires_at. Upload URLs accept POST with the raw file body; download URLs accept GET.

Use the code execution route for notebook-style Python snippets. The request body uses snake_case keys:

Terminal window
curl "$SANDBOX_URL/runtime/v1/code/run" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "print(\"hello\")\n2 + 3",
"language": "python",
"env_vars": { "PYTHONUNBUFFERED": "1" },
"timeout_ms": 30000
}'

The response includes execution.results, execution.logs.stdout, execution.logs.stderr, and execution.error. User-code exceptions are returned inside execution.error; transport/runtime failures use HTTP errors. Code runs in a persistent default Python context unless context_id selects another context. Use either language or context_id, not both.

Create a context:

Terminal window
curl "$SANDBOX_URL/runtime/v1/code/contexts" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "cwd": "/workspace", "language": "python" }'

List contexts:

Terminal window
curl "$SANDBOX_URL/runtime/v1/code/contexts" \
-H "Authorization: Bearer $SANDBOX_TOKEN"

Restart or remove a context:

Terminal window
curl "$SANDBOX_URL/runtime/v1/code/contexts/$CONTEXT_ID/restart" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN"
curl "$SANDBOX_URL/runtime/v1/code/contexts/$CONTEXT_ID" \
-X DELETE \
-H "Authorization: Bearer $SANDBOX_TOKEN"
Terminal window
curl "$SANDBOX_URL/runtime/v1/files/stat?path=/workspace/hello.py" \
-H "Authorization: Bearer $SANDBOX_TOKEN"
Terminal window
curl "$SANDBOX_URL/runtime/v1/files/move?path=/workspace/hello.py" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "to_path": "/workspace/main.py" }'
curl "$SANDBOX_URL/runtime/v1/files?path=/workspace/main.py" \
-X DELETE \
-H "Authorization: Bearer $SANDBOX_TOKEN"

Create, list, and remove directories:

Terminal window
curl "$SANDBOX_URL/runtime/v1/directories?path=/workspace/results" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN"
curl "$SANDBOX_URL/runtime/v1/directories?path=/workspace" \
-H "Authorization: Bearer $SANDBOX_TOKEN"
curl "$SANDBOX_URL/runtime/v1/directories?path=/workspace/results" \
-X DELETE \
-H "Authorization: Bearer $SANDBOX_TOKEN"

Processes run over a WebSocket at /runtime/v1/process. Send a start frame, then read started, stdout, stderr, and final exit frames as the process runs. Output data is base64 encoded.

{"type":"start","cmd":"python3","args":["/workspace/hello.py"],"cwd":"/workspace","timeout_ms":60000,"env":{"PYTHONUNBUFFERED":"1"}}

Example server frames:

{"type":"started","pid":"proc-123","os_pid":1234,"cursor":1}
{"type":"stdout","pid":"proc-123","data":"aGVsbG8K","cursor":2}
{"type":"exit","pid":"proc-123","exit_code":0,"error":null,"cursor":3}

Write stdin with:

{"type":"stdin","data":"aGkK"}

Send a signal over the socket:

{"type":"signal","signal":"SIGTERM"}

or with the REST signal helper:

Terminal window
curl "$SANDBOX_URL/runtime/v1/process/<pid>/signal" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"signal":"SIGTERM"}'

List running processes with a plain HTTP request:

Terminal window
curl "$SANDBOX_URL/runtime/v1/process" \
-H "Authorization: Bearer $SANDBOX_TOKEN"

Reconnect to a running process with GET /runtime/v1/process/<pid>/connect?since=<cursor> as a WebSocket. The gateway replays buffered frames after the cursor and then streams live output.

Start a PTY by sending a start frame with pty size over the process WebSocket:

{"type":"start","cmd":"/bin/bash","args":["-i","-l"],"stdin":true,"pty":{"cols":100,"rows":30}}

PTY output is sent as base64 encoded pty frames. Send input with the same stdin frame used by commands and resize with:

{"type":"resize","cols":120,"rows":40}

Watch a directory over WebSocket:

GET /runtime/v1/files/watch?path=/workspace&recursive=true

The stream sends event batches:

{"type":"events","events":[{"type":"modify","path":"/workspace/hello.py"}]}

The SDKs expose this as filesystem.watchDir(...), files.watch_dir(...), and Filesystem::watch_dir(...).

The data plane includes server-side Git helpers:

Terminal window
curl "$SANDBOX_URL/runtime/v1/git/clone" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "url": "https://github.com/acme/project.git", "path": "/workspace/project", "branch": "main", "depth": 1 }'
curl "$SANDBOX_URL/runtime/v1/git/status" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "path": "/workspace/project" }'
curl "$SANDBOX_URL/runtime/v1/git/restore" \
-X POST \
-H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "path": "/workspace/project", "paths": ["README.md"] }'

The same route family supports dangerously_authenticate, configure_user, init, branches, create_branch, delete_branch, add, commit, reset, restore, pull, push, remote_add, remote_get, set_config, get_config, and checkout. API payloads use snake_case keys such as env_vars, timeout_seconds, set_upstream, initial_branch, author_name, and dangerously_store_credentials.