Storage Quotas
Storage quotas let you cap how much each tenant can store. Use them to enforce your subscription tiers, prevent abuse, and build quota-aware UIs for your users.
How quotas work
Off by default
Tenants have no storage limit until you set one. They can upload as much as your plan allows.
Enforced at upload
Tenantbox checks the quota before issuing a presigned URL. Uploads that would exceed the limit are rejected before anything reaches Tenantbox storage.
Adjustable anytime
Set, update, or remove a quota at any time via the API. Changes take effect on the next upload attempt.
Per tenant
Quotas are per tenant, not per project. Each user gets their own limit independent of other users in the same project.
Common quota sizes
The API expects quota values in bytes. Here are common sizes for reference:
| Size | Bytes | Typical use case |
|---|---|---|
| 100 MB | 104857600 | Free tier — light users, small files |
| 500 MB | 524288000 | Free tier — moderate usage |
| 1 GB | 1073741824 | Starter plan |
| 5 GB | 5368709120 | Pro plan — typical SaaS user |
| 10 GB | 10737418240 | Pro plan — power users |
| 50 GB | 53687091200 | Enterprise — high volume tenants |
| No limit | null | Enterprise — unlimited storage |
Pre-upload quota check
While Tenantbox enforces quotas server-side, it's good practice to check usage before attempting an upload so you can show a meaningful error in your UI rather than catching a 413 response.
import requests
def can_upload(tenant_id: str, file_size_bytes: int, api_key: str) -> bool:
"""
Check if a tenant has enough quota before uploading.
Call this before requesting a presigned URL.
"""
response = requests.get(
f"https://api.tenantbox.dev/api/storage/tenants/{tenant_id}/usage/",
headers={"Authorization": f"Bearer {api_key}"},
)
response.raise_for_status()
data = response.json()
limit = data["storage_limit_bytes"]
used = data["storage_used_bytes"]
# No limit set — always allow
if limit is None:
return True
return (used + file_size_bytes) <= limit
# Usage
if can_upload("user_123", file.size, API_KEY):
# Proceed with upload
pass
else:
raise Exception("Storage quota exceeded")Tiered limits by plan
The most common pattern is to set a tenant's quota when they sign up or change their subscription plan. Here's how to implement that:
import requests
# Define your plan tiers in bytes
PLAN_LIMITS = {
"free": 524288000, # 500 MB
"pro": 5368709120, # 5 GB
"enterprise": 53687091200, # 50 GB
}
def set_tenant_quota(tenant_id: str, plan: str, api_key: str):
"""
Set a tenant's storage limit based on their subscription plan.
Call this when a user signs up or upgrades their plan.
"""
limit_bytes = PLAN_LIMITS.get(plan)
response = requests.patch(
f"https://api.tenantbox.dev/api/storage/tenants/{tenant_id}/limit/",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={"storage_limit_bytes": limit_bytes},
)
response.raise_for_status()
return response.json()
# New user signs up on free plan
set_tenant_quota("user_123", "free", API_KEY)
# User upgrades to pro
set_tenant_quota("user_123", "pro", API_KEY)
# Enterprise user — remove limit entirely
requests.patch(
"https://api.tenantbox.dev/api/storage/tenants/user_123/limit/",
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
json={"storage_limit_bytes": None},
)Building a usage bar
Show your users how much storage they've used with a simple progress bar. Fetch the usage data from your backend and render it client-side.
<!-- Vue component — storage usage bar -->
<script setup>
const props = defineProps({
used: Number, // storage_used_bytes
limit: Number, // storage_limit_bytes
})
const percent = computed(() => {
if (!props.limit) return 0
return Math.min(Math.round((props.used / props.limit) * 100), 100)
})
const barColor = computed(() => {
if (percent.value >= 90) return 'bg-red-500'
if (percent.value >= 70) return 'bg-yellow-500'
return 'bg-green-500'
})
function formatBytes(bytes) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
}
</script>
<template>
<div class="space-y-1.5">
<div class="flex justify-between text-sm">
<span>{{ formatBytes(used) }} used</span>
<span v-if="limit">{{ percent }}% of {{ formatBytes(limit) }}</span>
<span v-else>Unlimited</span>
</div>
<div v-if="limit" class="w-full h-2 bg-gray-200 rounded-full overflow-hidden">
<div
class="h-full rounded-full transition-all duration-500"
:class="barColor"
:style="{ width: percent + '%' }"
/>
</div>
<p v-if="percent >= 90" class="text-sm text-red-500">
Storage almost full. Delete files or upgrade your plan.
</p>
</div>
</template>Live preview
Running low on storage.
Storage almost full. Delete files or upgrade your plan.
Best practices
Set quotas at sign-up. Don't wait until a user hits an undefined limit — set a sensible default immediately when they create an account.
Warn users early. Show a warning in your UI when a tenant reaches 70% of their quota, not 99%. Give them time to act.
Adjust on plan change. When a user upgrades or downgrades their subscription, update their Tenantbox quota immediately via the API.
Handle deletions. When a user deletes content in your app, delete the corresponding files via the Tenantbox API so quota is freed up accurately.
Don't cache usage forever. Fetch fresh usage data before showing quota meters in your UI — stale data leads to confusing displays.
Always handle 413 responses. Even if you check quota before uploading, always catch 413 errors gracefully — race conditions can still occur.
Quota exceeded error
When an upload would exceed a tenant's quota, Tenantbox returns a 413 before generating the presigned URL — nothing is uploaded to Tenantbox storage.
{
"detail": "Storage quota exceeded for this tenant"
}Next — Usage & tracking
Learn how to monitor storage usage across all tenants in your project.