ReferenceRate limits

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.

RatePlan-based (see defaults below). Reported in X-RateLimit-Limit
WindowFixed 60-second window
BackendRedis (INCR + EXPIRE)
MeteringPer API key. Each key is metered independently against your plan's ceiling
Public endpointsSubject to a lower per-IP limit (60/min) to prevent abuse

Default ceilings by plan

Plan tierDefault ceiling
Free / Launch / Starter60 requests / minute
Pro / Scale / Growth300 requests / minute
Enterprise600 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:

HeaderTypeMeaning
X-RateLimit-LimitintegerMax requests per window — your plan's effective ceiling
X-RateLimit-RemainingintegerRequests left in the current window
X-RateLimit-ResetintegerSeconds 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:

  1. Respect X-RateLimit-Reset. Anything shorter just hits the limit again.
  2. Pad by 100ms or so to account for clock skew between your machine and Certivant's edge.
  3. 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

PatternEffect on rate limit
Use webhooks instead of polling GET /requests/:idCuts request volume by ~99% on typical integrations
Batch list calls with limit=100 instead of limit=205x 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 workloadsEach key is metered independently against your plan ceiling
Move long-running jobs off your webhook handlerAvoids 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.