#Volume HTTP
Use direct Volume file operations to operate on files in a Volume by volume ID, without mounting the Volume into a Sandbox first.
Start with the Go, Python, TypeScript SDKs, or the s0 CLI. The endpoint reference below is for advanced integrations and custom clients that need to call the HTTP and WebSocket API directly.
Requests still go through the normal gateway chain and team-scoped auth. Internally, storage-proxy lazily attaches the Volume and reclaims idle direct mounts later.
Base Model#
- Every path is resolved relative to the Volume root.
- Use absolute logical paths such as
/docs/readme.txt. - Read operations use
sandboxvolumefile:read. - Write, move, mkdir, and delete operations use
sandboxvolumefile:write.
The client helpers below call the same SandboxVolume file HTTP endpoints documented on this page. They do not require mounting the Volume into a Sandbox first.
SDK and CLI Quick Start#
Use these helpers for normal direct Volume file workflows.
goctx := context.Background() volumeID := "vol_abc123" _, err := client.MkdirVolumeFile(ctx, volumeID, "/docs", true) if err != nil { log.Fatal(err) } _, err = client.WriteVolumeFile(ctx, volumeID, "/docs/readme.txt", []byte("Hello from Volume HTTP")) if err != nil { log.Fatal(err) } summary, err := client.UploadVolumeDirectory(ctx, volumeID, "./dist", "/apps/current") if err != nil { log.Fatal(err) } fmt.Printf("uploaded files=%d bytes=%d\n", summary.Files, summary.Bytes) data, err := client.ReadVolumeFile(ctx, volumeID, "/docs/readme.txt") if err != nil { log.Fatal(err) } fmt.Println(string(data)) info, err := client.StatVolumeFile(ctx, volumeID, "/docs/readme.txt") if err != nil { log.Fatal(err) } fmt.Printf("size=%d mode=%s\n", info.Size, info.Mode) entries, err := client.ListVolumeFiles(ctx, volumeID, "/docs") if err != nil { log.Fatal(err) } fmt.Printf("entries=%d\n", len(entries)) _, err = client.MoveVolumeFile(ctx, volumeID, "/docs/readme.txt", "/docs/readme-old.txt") if err != nil { log.Fatal(err) } _, err = client.DeleteVolumeFile(ctx, volumeID, "/docs/readme-old.txt") if err != nil { log.Fatal(err) }
Client Method Mapping#
| Operation | Go | Python | TypeScript | CLI |
|---|---|---|---|---|
| Read file | ReadVolumeFile | client.volumes.read_file | client.volumes.readFile | s0 volume files cat |
| Stat path | StatVolumeFile | client.volumes.stat_file | client.volumes.statFile | s0 volume files stat |
| List directory | ListVolumeFiles | client.volumes.list_files | client.volumes.listFiles | s0 volume files ls |
| Write file | WriteVolumeFile | client.volumes.write_file | client.volumes.writeFile | s0 volume files write |
| Create directory | MkdirVolumeFile | client.volumes.mkdir | client.volumes.mkdir | s0 volume files mkdir |
| Move or rename | MoveVolumeFile | client.volumes.move_file | client.volumes.moveFile | s0 volume files mv |
| Delete path | DeleteVolumeFile | client.volumes.delete_file | client.volumes.deleteFile | s0 volume files rm |
| Upload local file | manual local read + WriteVolumeFile | client.volumes.write_file | client.volumes.writeFile | s0 volume files upload |
| Upload directory/archive | UploadVolumeDirectory / ImportVolumeArchive | client.volumes.upload_directory / client.volumes.import_archive | client.volumes.importArchive | s0 volume files upload --recursive |
| Download to local file | manual local write + ReadVolumeFile | client.volumes.read_file | client.volumes.readFile | s0 volume files download |
| Watch path | WatchVolumeFiles | client.volumes.watch_files | client.volumes.watchFiles | s0 volume files watch |
Directory Upload#
Use directory upload for build artifacts, framework bundles, and other multi-file trees.
gosummary, err := client.UploadVolumeDirectory(ctx, volumeID, "./dist", "/apps/current") if err != nil { log.Fatal(err) } fmt.Printf("uploaded files=%d dirs=%d symlinks=%d bytes=%d\n", summary.Files, summary.Directories, summary.Symlinks, summary.Bytes, )
FileInfo#
| Field | Type | Description |
|---|---|---|
name | string | File or directory name |
path | string | Logical path inside the Volume |
type | string | file, dir, or symlink |
size | integer | Size in bytes |
mode | string | Unix mode bits such as 0644 |
mod_time | string | Last modification time |
is_link | boolean | Whether the entry is a symlink |
link_target | string | Symlink target when available |
Write File#
Write binary content to a file. Creates parent directories automatically when needed.
/api/v1/sandboxvolumes/{id}/files?path=/docs/readme.txt
Request Body#
Raw bytes. For SDK and CLI usage, see the quick start and method mapping above.
Read File#
Read a file from a Volume.
/api/v1/sandboxvolumes/{id}/files?path=/docs/readme.txt
By default the response body is raw bytes.
If you set Accept: application/json, the API returns a base64 JSON payload instead:
Example response:
json{ "success": true, "data": { "content": "SGVsbG8sIFZvbHVtZSEK", "encoding": "base64" } }
Create Directory#
Create a directory with the same endpoint used for file writes.
/api/v1/sandboxvolumes/{id}/files?path=/project/src&mkdir=true&recursive=true
For SDK and CLI usage, see the create-directory row in the method mapping above.
Stat Path#
Return metadata for a file or directory.
/api/v1/sandboxvolumes/{id}/files/stat?path=/docs/readme.txt
Example response:
json{ "success": true, "data": { "name": "readme.txt", "path": "/docs/readme.txt", "type": "file", "size": 15, "mode": "0644", "mod_time": "2026-04-03T10:00:00Z", "is_link": false } }
List Directory#
List the direct children of a directory.
/api/v1/sandboxvolumes/{id}/files/list?path=/docs
Example response:
json{ "success": true, "data": { "entries": [ { "name": "readme.txt", "path": "/docs/readme.txt", "type": "file", "size": 15, "mode": "0644", "mod_time": "2026-04-03T10:00:00Z", "is_link": false }, { "name": "images", "path": "/docs/images", "type": "dir", "size": 0, "mode": "0755", "mod_time": "2026-04-03T10:00:00Z", "is_link": false } ] } }
Move or Rename#
Move or rename a file or directory.
/api/v1/sandboxvolumes/{id}/files/move
Request Body#
| Field | Type | Description |
|---|---|---|
source | string | Source logical path |
destination | string | Destination logical path |
For SDK and CLI usage, see the move-or-rename row in the method mapping above.
Delete File or Directory#
Delete a file or recursively delete a directory.
/api/v1/sandboxvolumes/{id}/files?path=/docs/readme.txt
For SDK and CLI usage, see the delete-path row in the method mapping above.
Watch for Changes#
Watch a path for file events over WebSocket.
/api/v1/sandboxvolumes/{id}/files/watch
WebSocket Protocol#
After connecting, send JSON messages to subscribe and unsubscribe.
Client messages:
| Action | Description | Example |
|---|---|---|
subscribe | Start watching a path prefix | {"action":"subscribe","path":"/docs","recursive":true} |
unsubscribe | Stop a previous watch | {"action":"unsubscribe","watch_id":"watch-123"} |
Server messages:
| Type | Description | Example |
|---|---|---|
subscribed | Subscription accepted | {"type":"subscribed","watch_id":"watch-123","path":"/docs"} |
event | File change event | {"type":"event","watch_id":"watch-123","event":"write","path":"/docs/readme.txt"} |
unsubscribed | Watch removed | {"type":"unsubscribed","watch_id":"watch-123"} |
error | Request rejected | {"type":"error","error":"invalid path"} |
Event names currently include:
createwriteremoverenamechmodinvalidate
Client Watch Examples#
gowatchCtx, cancel := context.WithCancel(context.Background()) defer cancel() events, errs, unsubscribe, err := client.WatchVolumeFiles(watchCtx, volumeID, "/docs", true) if err != nil { log.Fatal(err) } defer func() { _ = unsubscribe() }() for { select { case ev, ok := <-events: if !ok { return } if ev.Type == "event" { fmt.Printf("%s %s\n", ev.Event, ev.Path) } case err, ok := <-errs: if ok && err != nil { log.Fatal(err) } return } }
Notes#
GET /files,GET /stat, andGET /listrequire apathquery parameter.POST /fileswithmkdir=truecreates directories instead of writing file content.- Directory uploads use a tar stream internally and are not atomic. Deployment flows that require rollback should import into a fresh Volume or snapshot before switching traffic.
DELETE /filesis recursive for directories.- File size is currently capped by the direct HTTP handler. Large transfer workflows should prefer snapshots when appropriate.
Next Steps#
Snapshots
Create and restore point-in-time volume snapshots.
Fork
Fork volumes when workflows need isolated writable branches.