File Operations
Every bucket exposes the same three endpoints. The {bucket} segment is a name from your buckets[] array, or default for the implicit bucket.
Endpoints
| Method | Endpoint | Result |
|---|---|---|
POST |
/api/buckets/{bucket}/files |
201 with the generated storage key. |
GET |
/api/buckets/{bucket}/files/{key} |
200 with the bytes and a derived Content-Type. |
DELETE |
/api/buckets/{bucket}/files/{key} |
204, no body. |
An unknown bucket name answers 404 on all three, before anything else is read.
Upload
Send the file as multipart form data under the field name file.
curl -X POST https://app.example.com/api/buckets/documents/files \
-H "Cookie: $SESSION" \
-F "file=@report.pdf"{
"success": true,
"key": "9f3c1e2a-7b44-4d10-9e21-8a6f0c1d2e3b-report.pdf",
"size": 245760,
"mimeType": "application/pdf",
"filename": "report.pdf"
}| Outcome | Status |
|---|---|
| Accepted | 201 |
No file field in the body |
400 |
| Unsafe filename, or a MIME type the bucket bars | 400 |
| Over the bucket or global size limit | 413 |
Over STORAGE_MAX_TOTAL_SIZE |
507 |
| No session on a private bucket | 401 |
The key is not the filename. Sovrium stores each upload at <uuid>-<original-filename>, so two uploads of report.pdf never collide and no key is guessable. The original name is recovered from the suffix when the file is served, which is why downloads still arrive as report.pdf. Persist the key — it is the only handle to the file.
Storing at an Explicit Path
An optional path form field stores the file at that exact key instead, with no UUID prefix. This is how you land a file under a prefix listed in STORAGE_PUBLIC_PATHS:
curl -X POST https://app.example.com/api/buckets/default/files \
-F "file=@logo.png" -F "path=public/logo.png"The path must be relative, non-empty, and free of .., \ and null bytes. Because you chose the key, you also own collisions: a second upload to the same path overwrites the first.
Download
curl https://app.example.com/api/buckets/documents/files/$KEY -O| Outcome | Status |
|---|---|
| File exists and the caller may read it | 200 with the bytes. |
| Unknown key | 404 |
| Private bucket, no session, no signed URL | 404 — never 403, so keys stay unguessable |
Image keys accept transform parameters on this same URL; anything else returns 400 if you try. Every response carries Content-Disposition, X-Content-Type-Options: nosniff and a blocking Content-Security-Policy — see Upload Security.
Delete
curl -X DELETE https://app.example.com/api/buckets/documents/files/$KEY \
-H "Cookie: $SESSION"Success is 204 with no body; an unknown key is 404. Deleting also evicts every cached image transform derived from that key, so a stale thumbnail can never outlive its original.
Deleting a file directly does not clear any table record pointing at it. Removing a record's attachment is a separate path — see Lifecycle & Quotas.
Security Hardening
Moved to Upload Security — filename validation, MIME allow-lists, and the headers every served file carries.
File Lifecycle
Moved to Lifecycle & Quotas — what happens to a file when its record is deleted.
Large Uploads & Quota
Moved to Lifecycle & Quotas — size caps, the total-storage quota, and the admin usage endpoint.
Related Pages
- Buckets Overview — declaring the buckets these endpoints serve.
- Bucket Permissions — who may call each endpoint.
- Upload Security — what is rejected and why.
- Lifecycle & Quotas — retention, limits, quota.
- Signed URLs — access without a session.
- Attachment Fields & Storage — the same files, reached from a record.
Last updated August 11, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.