# Files and Processes

> Read and write sandbox files, list directories, and stream process output.

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

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

Data-plane requests use:

```text
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.

## Write a file

```bash
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.

## Write multiple files

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

```bash
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(...)`.

## Read a file

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

## Signed upload and download URLs

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

```bash
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`.

## Run code

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

```bash
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",
    "envs": { "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.

### Code contexts

Create a context:

```bash
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:

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

Restart or remove a context:

```bash
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"
```

## File metadata

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

## Move or delete a file

```bash
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"
```

## Directories

Create, list, and remove directories:

```bash
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"
```

## Stream a process

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.

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

Example server frames:

```json
{"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:

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

Send a signal over the socket:

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

or with the REST signal helper:

```bash
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:

```bash
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.

## PTY sessions

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

```json
{"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:

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

## Watch files

Watch a directory over WebSocket:

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

The stream sends event batches:

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

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

## Git helper routes

The data plane includes server-side Git helpers:

```bash
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
`envs`, `timeout_seconds`, `set_upstream`, `initial_branch`, `author_name`,
and `dangerously_store_credentials`.

[View this page on the web](https://docs.watasu.io/sandboxes/files-and-commands/)
