Rate limits
Certivant enforces a plan-based per-minute API rate limit — learn how to read rate-limit headers, handle 429 responses, retry correctly, and design your integration to stay under the limit.
Certivant enforces a per-minute API rate limit to protect the platform from runaway clients and noisy neighbours. Stay under the limit and you'll never see throttling; cross it and you get a 429 with headers that tell you exactly when to retry.
The limit
Your per-minute ceiling is set by your plan. The platform operator can also grant a per-customer override, which is always clamped to the plan cap. The effective number is whatever the X-RateLimit-Limit header reports on every response — read it rather than hard-coding a value.
| Rate | Plan-based (see defaults below). Reported in X-RateLimit-Limit |
| Window | Fixed 60-second window |
| Backend | Redis (INCR + EXPIRE) |
| Metering | Per API key. Each key is metered independently against your plan's ceiling |
| Public endpoints | Subject to a lower per-IP limit (60/min) to prevent abuse |
Default ceilings by plan
| Plan tier | Default ceiling |
|---|---|
| Free / Launch / Starter | 60 requests / minute |
| Pro / Scale / Growth | 300 requests / minute |
| Enterprise | 600 requests / minute |
These are defaults — your platform operator can adjust the cap per plan and grant your organization a higher per-customer limit (up to the plan cap). You can see your organization's effective limit, read-only, in the dashboard at KYC → Developer.
If you need a higher ceiling for production traffic, ask your account contact (or, on self-hosted deployments, your platform operator) to raise your plan cap or per-customer limit.
Response headers (on every authenticated response)
Every successful response includes the current rate-limit state:
| Header | Type | Meaning |
|---|---|---|
X-RateLimit-Limit | integer | Max requests per window — your plan's effective ceiling |
X-RateLimit-Remaining | integer | Requests left in the current window |
X-RateLimit-Reset | integer | Seconds until the window resets to full |
HTTP/1.1 200 OK
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 42
Content-Type: application/json
{ "data": [...], "pagination": {...} }
Read these in your client to back off proactively — don't wait for a 429 if you can see X-RateLimit-Remaining: 3.
The 429 response
When you exceed the limit, the next request returns:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 18
Content-Type: application/json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Max 120 requests per 60s. Retry in 18s."
}
}
X-RateLimit-Reset tells you exactly how long to wait. Use it instead of guessing.
Retry strategy
The simplest correct handler:
async function withRateLimit<T>(fn: () => Promise<Response>): Promise<T> {
while (true) {
const res = await fn();
if (res.status !== 429) {
if (!res.ok) throw await readError(res);
return res.json() as Promise<T>;
}
const retryAfterSec = Number(res.headers.get("X-RateLimit-Reset") ?? 60);
await sleep(retryAfterSec * 1000 + 100); // pad by 100ms for clock skew
}
}
Three properties to keep:
- Respect
X-RateLimit-Reset. Anything shorter just hits the limit again. - Pad by 100ms or so to account for clock skew between your machine and Certivant's edge.
- Cap your retries if the request isn't critical. Hammering for 5 minutes on a non-urgent list call wastes both ends.
Designing around the limit
| Pattern | Effect on rate limit |
|---|---|
Use webhooks instead of polling GET /requests/:id | Cuts request volume by ~99% on typical integrations |
Batch list calls with limit=100 instead of limit=20 | 5x fewer calls for the same data |
Cache GET responses for short windows (5-30s) | Cuts duplicate reads |
| Use separate keys for read-heavy vs. write-heavy workloads | Each key is metered independently against your plan ceiling |
| Move long-running jobs off your webhook handler | Avoids burst calls when a stack of events arrives |
The single biggest savings come from using webhooks. The number-one source of "I'm hitting the rate limit" support tickets is integrations that poll for state changes — don't do that, set up webhooks instead.
Burst behaviour
The limit is enforced as a fixed window: the first request starts a 60-second counter, and every request counts against your ceiling until that window resets. Spread sustained traffic evenly rather than firing your whole minute's budget in the first few seconds.
If you have a known burst pattern (e.g. a daily batch job that processes 500 records), space the calls out:
for (const record of records) {
await processOne(record);
await sleep(500); // 2 req/s — comfortably under typical ceilings
}
What happens when Redis is unreachable
If the rate-limit Redis instance is temporarily unreachable, Certivant fails open — it does not block your traffic. You'll see normal 2xx responses with X-RateLimit-* headers omitted. Once Redis is back, normal enforcement resumes. This means a Redis outage on our side does not interrupt your integration.
Webhook delivery is separate
The rate limit applies to your outbound calls to Certivant. Inbound webhook deliveries from Certivant to your endpoint do not consume any quota. There is no "webhook rate limit" you need to worry about.
Public endpoints
Endpoints that don't require an API key (GET /api/status, GET /api/kyc/public/status, the form submission endpoints) have a separate per-IP rate limit of 60 requests per minute. This applies to the IP address of the caller, not an API key. It's set conservatively because these endpoints are reachable without authentication; if your applicant-facing UI is hitting it, route the calls through your own backend instead so they share an IP under your control.
Errors
Every Certivant API error code explained — response shape, HTTP status mapping, per-code handling guidance, and a robust retry wrapper you can drop into your integration.
Schemas and enums
Canonical TypeScript types for every Certivant shared schema — request statuses, document types, check types, risk score shape, applicant and KYB entity fields, webhook envelope, and pagination.