Rate limits & quotas

Per-key throughput tiers, daily/monthly quotas, and per-IP ceilings — every number generated from config, with self-describing headers and a fail-open posture.

Drazill enforces limits at two levels: a per-key tier (throughput + quota) and a per-IP ceiling (a coarse shared-address guard that runs before authentication). Every table here is generated from the enforcing code/config and drift-gated — the numbers cannot rot.

Per-key throughput (tier)

Each API key has a rate-limit tier with a requests-per-minute ceiling. Tier changes are admin-reviewed (self-serve requests via POST /api/v1/api-keys/{id}/tier-request).

TierRequests / minute
FREE60
PRO600
ENTERPRISE6,000

Daily & monthly quotas

On top of the per-minute ceiling, keys carry daily and monthly request quotas (part of the Data & API usage layer). Usage is metered when DATA_API_USAGE_METERING_ENABLED is on.

TierRequests / dayRequests / month
FREE1,00020,000
PRO50,0001,000,000
ENTERPRISE2,000,00050,000,000

Metering fails open. If the usage store (Redis) has a blip, metering and quota enforcement fail open — a cache outage never denies a paying customer a request. The per-minute tier ceiling still applies. This is a deliberate customer-favourable posture (app/core/config.py).

Per-IP ceilings

Before a request is authenticated, a per-IP limiter applies a coarse ceiling by path category. These are per-IP, not per-user — many legitimate users share an address behind carrier-grade NAT, corporate proxies, and VPNs, so the ceilings are generous. Fine-grained throttling is the per-key tier above.

Path categoryRequests / windowWindow
auth6060s
waitlist3060s
app-shell30060s
wallet30060s
markets60060s
comments12060s
orders12060s
admin20060s
referrals1060s
general (shared-IP / NAT)60060s

The per-IP limiter also fails open if its store is unavailable — a blip must never block signup or trading.

Response headers

Limits are self-describing — you don’t have to guess:

HeaderMeaning
X-RateLimit-Limit / X-RateLimit-RemainingYour per-key throughput ceiling and what’s left.
X-Quota-*Daily / monthly quota usage and remaining.
Retry-AfterSeconds to wait after a 429.

These are exposed via CORS so a first-party browser client can read them (docs/api/cors-and-auth.md).

Handling 429

On a 429, back off for the Retry-After duration and retry. The SDKs do this for you — configure the retry budget and they retry 5xx and 429 responses, honouring Retry-After:

Python
1client = DrazillClient(
2 api_key="<DRAZILL_API_KEY>",
3 # Omit to use the default; see "Which environment?" below.
4 base_url="https://staging.drazill.com/api/v1",
5 timeout=10.0, # seconds
6 max_retries=3, # retry on 5xx and 429
7)

If you handle HTTP directly, read Retry-After and sleep before retrying; use an Idempotency-Key on any mutation you retry so a retry can never double-apply.

The full, per-plan rate/quota sheet is maintained in the repo at docs/legal/api-rate-plans.md — which carries a PENDING LEGAL REVIEW banner: the numbers there (and here) are the technical enforcement values from app/core/config.py and app/models/api_key.py, not a price sheet or a service-level guarantee. Pricing and contractual commitments are owner-defined and pending counsel review.