Drives: versioned files shared between runs
A drive is a named file tree of your account. Every change makes a new version; versions never change afterwards, so a run that mounted a version can always be reproduced. Runs mount drives at a path, read-only or read-write.
Create, write, read
curl https://shotcup.dev/api/v1/drives -H "Authorization: Bearer $SHOTCUP_API_KEY" \
-H "Content-Type: application/json" -d '{"name": "prices"}'
curl -X PUT https://shotcup.dev/api/v1/drives/prices/files -H "Authorization: Bearer $SHOTCUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"files": [{"path": "/eur.json", "content": "[1.08, 1.09]"}], "remove": ["/old.json"]}'A write takes files like a run does (text, encoding: "base64", an upload or an https URL) and paths to remove (a directory removes what is under it), and answers with the new version. Writing the files a version already has makes no version (created: false). GET /api/v1/drives/prices/files lists a version's files (the head unless ?version= names one; ?prefix=/dir narrows it) and ?path=/eur.json reads one file.
Every version has a treeSha256: the hash of its paths, file hashes, sizes and modes, so equal trees have equal hashes, in any drive.
Mount drives into runs
{
"code": "import fs from \"fs\";\nconst eur = JSON.parse(fs.readFileSync(\"/data/eur.json\", \"utf8\"));\nfs.writeFileSync(\"/results/sum.json\", JSON.stringify(eur.reduce((a, b) => a + b, 0)));",
"mounts": [
{ "drive": "prices", "path": "/data" },
{ "drive": "results", "path": "/results", "mode": "rw" }
]
}mode: "ro"(the default): every write under the mount point fails with anEROFSerror the script can catch; the drive never changes.mode: "rw": when the run succeeds, what it changed under the mount point becomes a new version (removed files included). A run that fails keeps nothing. The run'smountssay which version it mounted (baseVersion), which version its changes became (version) and whether that is now the drive's head.versionpins a version id; without it a mount uses the head when the run starts.
A saved sandbox takes mounts when it is created and mounts them into every run; a run's own mount at the same path replaces it. Mount points are absolute, never /, not inside one another, and no request file may lie under one.
Concurrent writers and merges
Runs that mount the same drive read-write at the same time never overwrite each other. The first to finish moves the head; the others become sibling versions of it, and their run's mount names the paths they changed differently from the head (conflictPaths). Nothing is lost: reconcile the open siblings when you choose to.
POST /api/v1/drives/:name/merge merges the open siblings (or the versions you name) with the head against their common ancestor:
- without
script: a three-way merge. A path one side changed takes that side; sides that changed it the same way agree. Paths the sides changed differently are conflicts:strategy: "fail"(the default) answers 409 with them and stores nothing,"head"keeps the head's side,"newest"the most recently written side. - with
script({ "code": ... }and the options of a run): a run that sees the common ancestor at/merge/base, the head at/merge/head, each sibling at/merge/versions/1,/2, ... (read-only), the conflicts in/merge/conflicts.json, and writes the merged tree to/merge/out, which starts as the head. When the run succeeds,/merge/outis the merge.
Either way the merge version's parents are the head and the merged siblings, and it becomes the head.
Versions, retention and pinning
GET /api/v1/drives/:name/versions lists versions newest first (?open=1: only the open siblings). Each drive keeps the newest versions your plan retains; older ones are pruned when a new one is written, except the head, open siblings (and what they were written against) and versions you pin with PATCH /api/v1/drives/:name/versions/:id { "pinned": true }. Bytes no kept version holds any more are deleted an hour later.
Quotas
| Plan | Drives | Stored | Files per version | Versions kept | Mounts per run | Mounted per run | Mounted files per run | Written per run |
|---|---|---|---|---|---|---|---|---|
| Free | 2 | 64 MiB | 512 | 5 | 1 | 4 MiB | 512 | 4 MiB |
| Pro | 25 | 5 GiB | 4,096 | 50 | 4 | 64 MiB | 4,096 | 12 MiB |
| Scale | 250 | 50 GiB | 16,384 | 500 | 8 | 128 MiB | 16,384 | 12 MiB |
| Platform | unlimited | 500 GiB | 16,384 | 1,000 | 8 | 128 MiB | 16,384 | 12 MiB |
Stored bytes count each stored file once however many versions hold it. A run's mounted bytes add to its filesystem quota rather than using it up. A read-write run that changes more than its plan's written bytes fails as a resource limit and stores nothing. A refusal is a 429 naming the limit (drive-count-limit, drive-storage-limit, drive-files-limit, mount-count-limit, mount-bytes-limit, mount-files-limit).
MCP and the SDK
The MCP server has list_drives, create_drive, delete_drive, read_drive, write_drive, list_drive_versions and merge_drive, and mounts on run_disposable, run_code and create_sandbox. The SDK has client.drives (create, list, get, delete, versions, files, read, write, merge, pin) and mounts on runs and sandboxes; the eve provider and the AI SDK session take mounts in the provider shape, { "/data": { "drive": "prices", "mode": "ro" } }.
This page as Markdown:/docs/drives.md