Quotas & plan limits
Single-file size, total storage, and API-key limits per plan.
Each account has plan limits enforced by the API: the largest single file, the total storage across all live files, and the number of API keys. The limits are real and checked on every upload — exceeding them returns a precise error before any bytes move.
Beta plan limits
| Limit | Beta plan |
|---|---|
| Largest single file | 100 MB |
| Total storage | 5 GB |
| API keys | 20 |
Beta gets the paid allowance for now
Billing is not wired up yet, so every account currently receives the paid-plan beta allowance for free. The quota machinery is fully active, so these limits apply now.
How limits are enforced
Single-file size
createUpload rejects a file larger than the
plan's single-file limit before minting any upload URL, with code
FILE_TOO_LARGE (HTTP 413).
This is separate from a route handler maxFileSize:
the plan limit is the hard ceiling for your account; a route constraint can set a
lower, per-route limit.
Total storage
Before reserving an upload, the API sums the size of all your live (ready and
pending) files. If the new file would push the total over the plan quota, it
rejects with QUOTA_EXCEEDED (HTTP 403).
Deleting files frees quota immediately — delete
removes the bucket object and soft-deletes the row, and soft-deleted files do not
count toward usage.
API keys
You can hold up to the plan's key limit at once (revoked keys do not count). Creating one beyond the limit fails — revoke an unused key first. See API keys.
Checking usage
Call usage to see where you stand:
const { usedBytes, fileCount, plan, limits } = await s3.usage();
const remaining = limits.maxTotalBytes - usedBytes;
console.log(`${plan}: ${fileCount} files, ${remaining} bytes free`);{
usedBytes: 524288000,
fileCount: 42,
plan: "beta",
limits: { maxFileBytes: 104857600, maxTotalBytes: 5368709120, maxApiKeys: 20 }
}The dashboard shows the same numbers on the Usage page. An AI agent can read
them too via the getUsage tool.
Handling quota errors
import { S3DeliveryError } from "s3delivery";
try {
await s3.upload(blob, { name: "big.zip" });
} catch (e) {
if (e instanceof S3DeliveryError) {
if (e.code === "FILE_TOO_LARGE") return showError("File exceeds the size limit.");
if (e.code === "QUOTA_EXCEEDED") return showError("Storage is full — delete some files.");
}
throw e;
}