Bytes go straight to storage through signed URLs, so large files never pass through the API.
| Method and path | Scope | What it does |
|---|---|---|
POST /uploads |
files:write |
Start an upload and get signed URLs |
GET /uploads/{id} |
files:write |
See where an upload stands |
POST /uploads/{id}/parts |
files:write |
Get fresh signed URLs for parts |
POST /uploads/{id}/complete |
files:write |
Finish the upload and get the file |
DELETE /uploads/{id} |
files:write |
Abort an unfinished upload |
Flow
- Start.
POST /uploadswith{ "name", "size", "folder_id"?, "content_type"?, "sha256"?, "on_conflict"?, "modified_at"? }. The answer is201with signed URLs. - Send the bytes. In mode
single,PUTthe whole file touploads[0].url. In modemultipart,PUTpart N to its URL. Each part ispart_sizebytes. Ask for more URLs withPOST /uploads/{id}/partsand{ "from_part": 1, "count": 100 }(up to 100 at a time). - Complete.
POST /uploads/{id}/complete. It answers409 incompletewithdetails.missing_partsuntil every part is stored. It is safe to repeat.
Same name already there
The same rules as the app apply. Free renames the new file to name (2). Pro saves a new version of the existing file: the answer has kind: "version" and file_id, and complete returns the file with version_created. A clash the app would ask about is 409 conflict with the choices in details. Identical content answers 200 with skip. Send on_conflict to choose.
Checksums and times
complete takes an optional body { "sha256", "modified_at", "multipart_id" }. Up to 100 MiB the server hashes the stored object. A match is stored as sha256_source: server. A mismatch is 422 integrity_mismatch and the file moves to the bin, so upload it again. Above 100 MiB your claim is stored as client.
Empty files
size: 0 creates the empty file at once and answers 201 with { "action": "created", "kind": "empty", "file": { ... } }. There is no upload step. An empty file cannot become a new version (409 empty_version_not_supported), so send on_conflict as separate or skip.
Limits
Storage and plan limits apply as in the app: 413 storage_full, 413 file_too_large. A transfer-held item follows the same transfer rules. Uploads count against the files rate limit (600 a minute per token).