Rate limits & quotas

Every request passes five checks in order: auth-failure IP bucket, per-key requests-per-minute, per-customer requests-per-minute, per-customer concurrent request cap, and per-customer daily bandwidth (upload and download tracked separately).

Default tiers

Your administrator sets your limits per customer; new customers default to the free tier. Talk to sales to raise any dimension.

                       Free tier     Paid tier
Requests / minute      60            600
Concurrent requests    5             25
Upload per day         1 GB          50 GB
Download per day       10 GB         500 GB

Cost weights

Requests are billed against the per-minute counter by weight, so a batch of uploads consumes budget faster than the same number of metadata GETs.

GET single file / metadata / job status     1
List containers / list items / usage         3
Direct-URL upload/download URL minting       3
Upload / download / upload-session chunk     5
Copy or move job enqueue                    10

A key rated at 60 requests/minute can therefore make 60 metadata GETs, 20 listings, 12 uploads, or 6 copy enqueues per minute — or any mix summing to 60.

Per-key overrides

Individual API keys can carry their own requestsPerMinute and concurrencyLimit — useful when one application should be more restrictive than the customer-wide cap. A request must pass BOTH the per-key bucket AND the per-customer bucket. Adding a second API key does not raise your customer-wide ceiling.

Denial responses

RPM denials return 429 RATE_LIMITED. Concurrency and bandwidth denials return 429 QUOTA_EXCEEDED with details.reason distinguishing the cause.

HTTP/1.1 429 Too Many Requests
Retry-After: 23

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests. Slow down and retry later.",
    "details": {
      "reason": "rpm",
      "scope": "customer",
      "limitPerMinute": 600,
      "costWeight": 5
    }
  },
  "requestId": "req_..."
}
HTTP/1.1 429 Too Many Requests
Retry-After: 43200

{
  "success": false,
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "The storage quota would be exceeded by this operation.",
    "details": {
      "reason": "bandwidth",
      "direction": "up",
      "limitBytesPerDay": 53687091200,
      "usedBytes": 53690000000
    }
  },
  "requestId": "req_..."
}

Storage quotas

Your storage account has a byte quota; individual containers may carry an additional quota. Storage quotas are checked before an upload starts — uploads that would exceed the quota fail fast with QUOTA_EXCEEDED (413) and details.reason: "storage". Check your current usage at GET /api/v1/storage/usage.

Upload size limit

The synchronous upload limit is deployment-configured (default 4 MB on Vercel, where request bodies are capped by the platform). Oversized uploads return FILE_TOO_LARGE with the limit in error.details.maxUploadBytes. Use the resumable upload session flow for larger files (see the Files docs).

Direct-URL uploads and downloads

When you use POST /containers/{c}/direct-uploads or POST /files/{id}/download-url, bytes flow client ↔ storage provider without hitting the platform. The request that mints the URL still spends 3 cost units and reserves bandwidth against your daily cap right away — the declared upload size for uploads, the file's actual size for downloads. Cancelling a direct-upload session with DELETE releases the storage quota reservation; daily bandwidth accounting does not roll back because the URL may already have been used.

Client guidance

Respect Retry-After and back off exponentially on 429. A well-behaved client that starts at 1 s and doubles on each 429 (capped at 30 s) will not accumulate a retry-storm and will typically stabilize below the limit within three retries.

For batch workloads, cap client concurrency at or below your customer's concurrency limit — going higher just wastes round-trips on QUOTA_EXCEEDED reason=concurrency.

Backend throttling

The storage backend applies its own throttling. The platform retries transient throttling internally; when throttling persists, you receive STORAGE_THROTTLED (429) with a Retry-After header. Back off accordingly.