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

1

Request presigned URL

Your server calls POST /api/storage/upload/ with the tenant ID and filename.

2

Upload to Tenantbox storage

Your client PUTs the file binary directly to the presigned URL. No Tenantbox server involved.

3

File confirmed

Tenantbox records the file and tracks storage used for that tenant automatically.

Endpoint

POST /api/storage/upload/

Request body

FieldTypeRequiredDescription
tenant_idstringRequiredYour user's ID from your own database. Tenant is created automatically on first upload.
filenamestringRequiredOriginal filename including extension e.g. contract.pdf
content_typestringOptionalMIME type of the file e.g. application/pdf, image/jpeg. Auto-detected from filename if omitted.

Response

FieldTypeDescription
presigned_urlstringTime-limited URL to PUT the file to. Valid for 1 hour.
file_pathstringThe Tenantbox storage path where the file is stored. Save this — you need it to download or delete the file later.
tenant_idstringThe tenant ID you passed in, echoed back for confirmation.
is_new_tenantbooleantrue 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
Sample response — Step 1
{
  "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

401Unauthorized
Invalid or missing API key.
{ "detail": "Unauthorized" }
400Bad Request
Missing required fields in request body.
{ "detail": "filename is required" }
413Quota Exceeded
Tenant has reached their storage limit.
{ "detail": "Storage quota exceeded for this tenant" }

Next — Downloading files

Learn how to generate presigned download URLs to serve files to your users.

Downloads →