Rate Limits
Per-tier quotas, sliding-window limits, and the 429 + upgrade_url contract.
brandRNA enforces three independent layers of rate-limiting:
- Per-tier monthly quota — your account's contractual call budget.
- Per-key sliding-window burst limit — a short-window guard against runaway loops.
- Per-IP demo limiter — only applies to the homepage demo widget, never to authenticated traffic.
Read on for the contract details.
Quotas by tier
| Tier | Monthly calls | Burst (per minute) | Overage behaviour |
|---|---|---|---|
| Free | 100 | 60 | Hard 429 with upgrade_url once monthly cap is hit. |
| Paid | Usage-based | 300 | Soft — meter ticks up, billed via Stripe; user-set $-cap can pause. |
See the pricing page for current per-call rates on the paid tier. Cached calls (cache-hit responses) are always billed at $0, regardless of tier.
Cached calls are free in every tier. The metadata.cached: true field
on a brand-pack response is your signal that you didn't burn quota.
The 429 response
When the monthly cap (free tier) or per-minute burst is hit, the server returns:
{
"error": "quota_exceeded",
"code": "quota_exceeded",
"details": {
"tier": "free",
"monthly_used": 100,
"monthly_limit": 100,
"upgrade_url": "https://brandrna.com/pricing"
}
}upgrade_url is the canonical deep-link to the upgrade flow. Surface it
directly to your end-users — don't hard-code the URL in your client; we
may rotate it for A/B tests.
The 429 response does not include a Retry-After header today. If
you need server-driven retry timing, pin to the
changelog — it'll ship in a future minor release.
Sliding-window mechanics
The burst limiter is a sliding window keyed by API key and remote IP, backed by a counter store (in-memory locally, Upstash Redis in production). The window is approximately 60 seconds; specifics:
- Each request advances a counter under
(api_key, ip)and(api_key). - When either counter exceeds its tier limit within the trailing 60s, the request returns 429 immediately — no queueing.
- Counters expire automatically; no garbage collection is required.
The implementation lives in app/api/middleware/rate_limit.py. It's a
single middleware so the same rate-limit logic applies to every REST
endpoint without per-route config.
Why no fixed-window?
Fixed windows reset at clock boundaries (e.g. on the minute), which lets clients double the effective limit by bursting at the boundary. The sliding-window prevents that by always measuring the trailing 60s.
Spending cap (paid tier)
On the paid tier, you can set a monthly $-ceiling from the Billing dashboard. When the meter crosses the cap:
- The server auto-pauses API calls for that account — every
authenticated request returns 429 with
details.tier: "paid"anddetails.reason: "spend_cap_reached". - We send a warning email at 80% of the cap so you can adjust before the pause kicks in.
- Lift the pause by raising the cap or waiting for the next billing cycle.
This is opt-in. Without a cap set, paid-tier traffic runs uncapped and bills at the metered rate.
Demo widget (homepage)
The interactive demo at brandrna.com is not authenticated. It uses a 5-layer abuse defence:
- Cloudflare Turnstile bot challenge (per submission).
- Per-IP sliding-window quota (separate from authenticated limits).
- Per-IP daily call cap.
- Account-wide spend ceiling — cuts off all demo traffic at $10/day.
- Aggressive timeout on the scrape itself.
If you find yourself hitting the demo limits, sign up for a free key — you'll skip the abuse layer entirely and get 100 calls/month.
Practical patterns
- Cache aggressively on your end too. Even at $0/cached-call, an HTTP round-trip costs ~80ms. See Cache brand packs in your own backend.
- Batch where possible. If you're enriching a list of domains, fan out with limited concurrency (5–10 in flight). The burst limiter enforces this anyway, but explicit concurrency gives cleaner errors.
- Watch
metadata.cached. Afalsehere means a fresh extraction was triggered — counts toward your quota even if the response was fast. - Backoff on 429. Wait at least 60s before retrying; the trailing window will have rolled off.
What's next
- Read the error shape for the full 429 / 5xx matrix.
- Cross-link to authentication for key lifecycle and labelling.
- Run the Quickstart end-to-end if you haven't.