Rate limits & quotas
Two independent ceilings: a short-window rate limit that smooths bursts, and a plan quota that caps total volume.
Rate limits
Fixed windows, counted server-side. Exceeding either returns 429.
The per-IP limit is checked before the key is looked up, so an unauthenticated flood cannot be turned into database load. If you run many keys behind one egress address, the per-IP ceiling is the one you will meet first.
Every successful response carries X-RateLimit-Remaining — requests left in the current window for that key. A 429 carries Retry-After in seconds.
Plan quotas
Quota is shared between the API and the app: a file scrubbed in Studio and one sent to /v1/scrub both count once.
Staying inside the limits
At 120 requests per minute per key, a single worker sending sequentially will never approach the rate limit. Concurrency is what trips it. A small fixed concurrency, with Retry-After honoured, is more effective than a large pool that spends its time being throttled:
// Four in flight sits comfortably under 120/min even at ~0.5s per call,// and degrades gracefully if the endpoint slows down.const CONCURRENCY = 4;async function scrubAll(files) {const queue = [...files];const results = [];await Promise.all(Array.from({ length: CONCURRENCY }, async () => {while (queue.length) {const file = queue.shift();results.push(await scrubWithRetry(file));}}));return results;}
See Errors for a retry helper that reads Retry-After.
Checking your usage
X-PurifyAI-Usage-Count on each response gives the lifetime call count for that key. Current period usage against your plan is shown in Billing.
error field distinguishes them: rate_limited clears within the minute, quota does not clear until the day or month rolls over. Backing off on a quota error just delays the same failure.