Files
Put code and data into a sandbox, and get results back out, without building shell commands by hand. Four operations on one endpoint: write, read, list, delete.
You can always move files with exec and a heredoc, and for a three-line script that is fine. It stops being fine as soon as the content contains a quote, a backtick, a dollar sign or a byte that is not text — at that point you are writing an escaping routine, and the shell is going to win. These endpoints transfer content out of band so nothing is ever interpreted.
Writing a file
PUT https://sandbox-as-a-service.com/v1/sandboxes/{id}/files
curl -sS -X PUT https://sandbox-as-a-service.com/v1/sandboxes/$SBX/files \
-H "Authorization: Bearer $AAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"path":"analyse.py","content":"import pandas as pd\nprint(pd.__version__)\n"}'import requests
requests.put(
f"{API}/sandboxes/{sandbox_id}/files",
headers=headers,
json={"path": "analyse.py", "content": script_source},
).raise_for_status()await fetch(`${API}/sandboxes/${sandboxId}/files`, {
method: "PUT",
headers,
body: JSON.stringify({ path: "analyse.py", content: scriptSource }),
});
The response confirms the resolved path and the number of bytes written:
{
"object": "file",
"path": "/workspace/analyse.py",
"bytes": 42
}
Binary content
Set encoding to base64 and send the encoded bytes. Use this for
anything that is not UTF-8 text — images, archives, compiled artefacts, model weights.
{"path": "input.png", "content": "iVBORw0KGgoAAA...", "encoding": "base64"}
Reading a file
GET https://sandbox-as-a-service.com/v1/sandboxes/{id}/files?path=out/report.json
Returns the content as UTF-8 text by default. Add &encoding=base64 to get the
raw bytes back, which is what you want for anything your code produced that is not text.
curl -sS "https://sandbox-as-a-service.com/v1/sandboxes/$SBX/files?path=out/report.json" \
-H "Authorization: Bearer $AAS_API_KEY"
Listing a directory
GET https://sandbox-as-a-service.com/v1/sandboxes/{id}/files?path=/workspace&list=true
Returns every entry beneath the path, recursively, with its type and size. This is the call an agent makes after running something, to find out what the run produced:
{
"object": "list",
"path": "/workspace",
"entries": [
{ "name": "analyse.py", "type": "file", "size_bytes": 42 },
{ "name": "out", "type": "dir", "size_bytes": 4096 },
{ "name": "out/report.json", "type": "file", "size_bytes": 2214 }
],
"truncated": false
}
Listings recurse up to 12 levels and 2,000 entries. If a tree is
larger than that — you listed a directory containing node_modules — the response carries
"truncated": true rather than quietly handing back a short list. Pass
&recursive=false for one level only.
Deleting
DELETE https://sandbox-as-a-service.com/v1/sandboxes/{id}/files?path=out/report.json
Removes a file or a directory and its contents. Deleting /workspace itself is
refused — destroy the sandbox instead, or delete its contents. In practice you rarely need this:
the sandbox is thrown away shortly afterwards regardless.
Where paths resolve
| You write | It resolves to |
|---|---|
analyse.py | /workspace/analyse.py |
out/report.json | /workspace/out/report.json |
/workspace/analyse.py | /workspace/analyse.py |
/tmp/scratch | /tmp/scratch |
Relative paths resolve under /workspace, the working directory exec uses by default,
so the common case needs no prefix. Absolute paths are honoured as given. Parent-directory segments
are rejected outright rather than normalised, so a path assembled from user input cannot walk out of
where you meant it to go. Intermediate directories are created for you on write.
Files are written as the unprivileged sandbox user, the same user your commands run
as. Paths that user cannot write are an error, not a silent failure.
Limits
| Limit | Value |
|---|---|
| Maximum file size, read or write | 8 MiB |
| Maximum path length | 4096 characters |
| Requests per minute | 120, shared with the rest of the API |
For anything larger than 8 MiB, have the sandbox fetch it directly — it has
outbound network access, so curl from object storage is faster than a round trip
through this API and is not subject to the cap.
A complete round trip
Write a script, run it, read back what it produced:
SBX=$(curl -sS -X POST https://sandbox-as-a-service.com/v1/sandboxes \
-H "Authorization: Bearer $AAS_API_KEY" -H "Content-Type: application/json" \
-d '{"size":"small"}' | jq -r .id)
curl -sS -X PUT https://sandbox-as-a-service.com/v1/sandboxes/$SBX/files \
-H "Authorization: Bearer $AAS_API_KEY" -H "Content-Type: application/json" \
-d '{"path":"run.py","content":"import json\njson.dump({\"answer\": 42}, open(\"out.json\",\"w\"))\n"}'
curl -sS -X POST https://sandbox-as-a-service.com/v1/sandboxes/$SBX/exec \
-H "Authorization: Bearer $AAS_API_KEY" -H "Content-Type: application/json" \
-d '{"command":"python3 run.py"}'
curl -sS "https://sandbox-as-a-service.com/v1/sandboxes/$SBX/files?path=out.json" \
-H "Authorization: Bearer $AAS_API_KEY" | jq -r .content
curl -sS -X DELETE https://sandbox-as-a-service.com/v1/sandboxes/$SBX -H "Authorization: Bearer $AAS_API_KEY"
From an agent
The MCP server exposes the same operations as
write_file, read_file and list_files tools, so an agent can
put a script somewhere, run it, and go looking for what it produced without any of this being
written as HTTP calls.
Why not just use exec with a heredoc?
You can, and for short plain-text scripts it is perfectly reasonable. The failure mode is content you did not write yourself: a quote, a backtick or a $ in generated code changes what the shell does, and binary content cannot survive at all. These endpoints move the content out of band, so the sandbox never parses it.
Do files survive after the sandbox is destroyed?
No. Nothing in a sandbox is persistent — the machine and its disk are destroyed on expiry or on delete. Read anything you want to keep before that happens, or have the sandbox upload it somewhere while it is running.
Can I read files outside /workspace?
Yes, with an absolute path, subject to what the unprivileged sandbox user is allowed to read. What you cannot do is escape a relative path with .. — those are rejected rather than normalised.
Is there a directory upload or an archive endpoint?
Not yet. For a tree, either write the files individually or write one archive and unpack it with exec — tar and unzip are in the image.