Rate limits
The Regulatory Snapshot API rate-limits per API key. Limits are bucketed by category (reads, scans, exports) — heavy reads don’t eat into your scan budget, and vice versa. Limits return draft-7 RateLimit-* headers on every response, plus Retry-After when you cross the line.
This page lists the buckets, the headers we set, and the conventional retry pattern. If you’ve configured the SDKs, you can skim — they handle 429s for you.
The buckets
| Bucket | Limit | Window | Applies to |
|---|---|---|---|
reads | 60 | 1 minute | GET /v1/snapshots/*, GET /v1/scans/:id, GET /v1/exports/:id, GET /v1/usage |
scans | 6 | 1 hour | POST /v1/scans |
exports | 30 | 1 hour | POST /v1/exports |
key_mgmt | 10 | 1 minute | /v1/keys/* (dashboard, per-user) |
All buckets are per API key (except key_mgmt, which is per Clerk user — keys aren’t relevant on the key-management surface itself).
[!TIP] Need more headroom? Spinning up additional keys does not raise your aggregate scan or export budget — limits scale linearly with the number of active keys, but budget (cents/USD) is per-account. For an architecture serving many tenants, sharding by API key per tenant is the conventional pattern.
The SSE stream (GET /v1/scans/:id/stream) is not rate-limited; one long-lived stream per scan is the expected pattern.
Response headers
Every response carries draft-7 RateLimit-* headers (IETF draft ):
RateLimit-Limit: 60
RateLimit-Remaining: 41
RateLimit-Reset: 38RateLimit-Limit— total requests allowed in the current window.RateLimit-Remaining— how many are left.RateLimit-Reset— seconds until the window resets.
When you cross the limit, the response is:
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 60
{
"type": "https://api.regsn.app/problems/rate_limited",
"title": "Rate limit exceeded",
"status": 429,
"code": "rate_limit_exceeded",
"detail": "Rate limit exceeded for reads bucket. Retry after 60s.",
"request_id": "req_…"
}The Retry-After header is in seconds. It is conservative (the full window length), not a precise reset time — wait at least that long.
How to handle 429s
The conventional pattern is:
- Look at
Retry-After(or, equivalently,RateLimit-Reset). - Sleep that many seconds.
- Retry the same request.
If you’re sending an Idempotency-Key on a POST (you should be — see Idempotency), retrying with the same key is safe: the server will either replay the cached response or actually re-run, depending on whether the original made it through.
while true; do
resp=$(curl -s -o body -w '%{http_code}' \
https://api.regsn.app/v1/scans \
-H "Authorization: Bearer $REGSN_API_KEY" \
-H "Idempotency-Key: $IDEM" \
-H "Content-Type: application/json" \
-d '{"jurisdictions":["UK"],"areas":["AML"],"horizon":12}')
if [[ "$resp" != "429" ]]; then break; fi
retry=$(grep -i '^retry-after:' headers | awk '{print $2}' | tr -d '\r')
sleep "${retry:-30}"
doneBoth SDKs do this automatically — up to 3 retries with exponential backoff and Retry-After honoring.
Why the limits are what they are
reads = 60/minis comfortably above any “render a dashboard” workload but stops accidental loops dead.scans = 6/hourmatches the cost reality — a scan is the expensive primitive. If you need more,mode=asyncand a queue is the right shape, not a higher rate limit.exports = 30/hourlets you fan out a single snapshot to multiple artifacts (PDF + tearsheet + audio + deck = 4 exports) several times per hour.
If you’re hitting limits regularly, two questions to ask:
- Are you polling too aggressively? A 5-second poll on
GET /v1/scans/{id}is plenty. - Should you be using SSE instead?
/v1/scans/{id}/streamdoesn’t count against any bucket.
Diagnosing hot keys
/v1/usage?group_by=key shows you per-key request counts and costs. If one key is dominating, that’s probably the one hitting 429s — consider sharding the workload by tenant or by use-case so each tenant gets its own bucket.
[!TIP] Pair
Idempotency-Keywith your retry-after handling. Retrying aPOSTwith the same idempotency key after a 429 is safe — the server either replays the cached response (if the first attempt did make it through) or queues a fresh job. Without an idempotency key, a 429-then-success retry can double-bill you.
See also
- Errors — full problem-details schema;
429 rate_limit_exceeded. - Idempotency — pair idempotency keys with retries to keep 429 retries safe.
- Usage —
group_by=keyto find hot keys. - Glossary — rate limit, idempotency key.