Skip to main content
View as Markdown

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.

>_ terminal
curl -X POST https://app.example.com/api/buckets/documents/files \
  -H "Cookie: $SESSION" \
  -F "file=@report.pdf"
app.json
{
  "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

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:

>_ terminal
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

>_ terminal
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

>_ terminal
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.

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.

Built with Sovrium