File Uploads
Tenantbox uses presigned URLs to upload files. Your file goes directly from the client to Tenantbox storage - it never passes through Tenantbox's servers, keeping uploads fast and your server free.
How uploads work
Request presigned URL
Your server calls POST /api/storage/upload/ with the tenant ID and filename.
Upload to Tenantbox storage
Your client PUTs the file binary directly to the presigned URL. No Tenantbox server involved.
File confirmed
Tenantbox records the file and tracks storage used for that tenant automatically.
Endpoint
Request body
| Field | Type | Required | Description |
|---|---|---|---|
tenant_id | string | Required | Your user's ID from your own database. Tenant is created automatically on first upload. |
filename | string | Required | Original filename including extension e.g. contract.pdf |
content_type | string | Optional | MIME type of the file e.g. application/pdf, image/jpeg. Auto-detected from filename if omitted. |
Response
| Field | Type | Description |
|---|---|---|
presigned_url | string | Time-limited URL to PUT the file to. Valid for 1 hour. |
file_path | string | The Tenantbox storage path where the file is stored. Save this — you need it to download or delete the file later. |
tenant_id | string | The tenant ID you passed in, echoed back for confirmation. |
is_new_tenant | boolean | true if this is the first upload for this tenant ID. Useful for onboarding flows. |
Code examples
# Step 1 — Get presigned upload URL
curl -X POST https://api.tenantbox.dev/api/storage/upload/ \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "user_123",
"filename": "contract.pdf",
"content_type": "application/pdf"
}'
# Step 2 — PUT the file directly to Tenantbox storage using presigned_url from above
curl -X PUT "<presigned_url>" \
-H "Content-Type: application/pdf" \
--data-binary @contract.pdf{
"presigned_url": "https://xxx.r2.cloudflarestorage.com/multitenantstorage/projects/abc.../tenants/def.../xyz_contract.pdf?X-Amz-...",
"file_path": "projects/abc.../tenants/def.../xyz_contract.pdf",
"tenant_id": "user_123",
"is_new_tenant": false
}Important notes
Presigned URLs expire in 1 hour
Request the presigned URL and upload the file immediately. Don't store presigned URLs for later use — request a fresh one when you need to upload.
Save the file_path
The file_path in the response is the permanent identifier for the file. Store it in your database — you need it to generate download URLs and to delete the file later.
Content-Type must match
The Content-Type header you send in the PUT request to Tenantbox storage must match the content_type you passed in Step 1. Mismatches will cause the upload to fail.
Tenants are created automatically
You don't need to create tenants in advance. The first time you upload a file for a new tenant_id, Tenantbox creates the tenant automatically. is_new_tenant: true in the response tells you when this happens.
Error responses
{ "detail": "Unauthorized" }{ "detail": "filename is required" }{ "detail": "Storage quota exceeded for this tenant" }Next — Downloading files
Learn how to generate presigned download URLs to serve files to your users.