Rate limits
Two independent limiters run on the API:
| Surface | Bucket | Limit |
|---|---|---|
| Authenticated endpoints (/api/v1/* with a key) | Per API key | 60 requests / rolling 60s |
| GET /verify/:code (public) | Per caller IP | 60 requests / rolling 60s |
Limits are per-key (or per-IP), not per-endpoint — every call your key makes shares the same 60-request window. The public verify limiter is separate: it applies even though the endpoint takes no key, so a verification widget embedded on your site is limited by the visitor's IP, not by your account.
Headers
Every /api/v1/* response carries the current window state — authenticated responses and public /verify/:code responses alike:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1735689660
X-RateLimit-Reset is a Unix timestamp (seconds) marking the end of the current window.
429 responses
When you exceed the limit, you get:
HTTP/1.1 429 Too Many Requests
Retry-After: 27
Content-Type: application/json
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Slow down and try again."
}
}
Wait Retry-After seconds before retrying. Implement exponential backoff with jitter if you're batching high volumes.
Best practices
- Batch issuance. Use
recipients: [...](up to 500 per call) instead of loopingrecipient:singletons. One batch call costs one request against your limit. - Cache reads.
GET /templatesandGET /templates/:idresponses are stable — cache them in your integration. - Cache verification results. If you render a public verification page, cache by code rather than calling
/verify/:codeon every page view — the IP limiter is shared across all your visitors behind a proxy. - Respect
Retry-After. Hammering during rate-limit windows extends the block.
Need higher limits?
Reach out via contact with your use case and expected RPS.