How verification API rate limits work, which errors to retry, how to back off properly and how to keep retries from creating duplicate checks.
Most verification integrations work well in testing and then hit their first real problem on a busy day. A seller promotion goes out, a few hundred people sign up at once, and the onboarding service starts firing identity, business and screening checks as fast as it can. Some calls come back with 429. A few time out. The retry logic, written in an afternoon, sends them all again at the same moment.
None of this is unusual, and none of it is hard to handle once you know what the API is telling you. This post covers how per-minute limits and monthly allowances work, which errors are worth retrying, how to back off without making things worse, and how to stop retries from turning into duplicate checks.
Rate limits are not there to make integration harder. A verification call usually does real work behind the scenes: reading a document, comparing a face, querying a register or screening a name against sanctions and PEP lists. Some of that work depends on upstream sources with their own limits. A cap per key keeps one integration's burst from slowing down everyone else, and it keeps costs predictable for both sides.
For a marketplace, the useful way to think about limits is as a design input. If you know your per-minute ceiling, you can decide in advance what happens when a sign-up spike exceeds it: queue the work, spread it out, and tell the seller their check is in progress rather than showing them an error.
The rate limits and quotas page in our docs sets out the detail. In short, each API key has a per-minute limit set by its plan, and responses to key-authenticated calls carry three headers:
X-RateLimit-Limit: requests allowed per minute for this keyX-RateLimit-Remaining: requests left in the current minuteX-RateLimit-Reset: seconds until the minute resetsWhen you go over the limit, the API returns HTTP 429 with error: rate_limit_exceeded, a retryAfterSeconds field in the body and a Retry-After header. The instruction is simple: wait that long, then try again.
Separately from the per-minute limit, live checks count against your plan's monthly allowance. Where a finite allowance applies, responses carry X-Quota-Limit, X-Quota-Remaining and X-Quota-Reset. Allowances reset at 00:00 UTC on the first of each month. Sandbox calls do not count towards the allowance, which matters when you are load testing.
Some endpoints, including email and phone checks and adding a subject to ongoing monitoring, also count towards a daily cap. Going over that returns 429 with code: DAILY_CAP_EXCEEDED, and it resets at midnight UTC. A daily cap will not clear in a few seconds, so your code should tell it apart from a per-minute 429 and not keep retrying it all afternoon.
The cheapest 429 is the one you never receive. Because every key-authenticated response carries X-RateLimit-Remaining, a client can slow itself down before it reaches zero.
A simple pattern works for most marketplaces:
This keeps bursts inside your limit without anyone noticing, and it means a sign-up spike becomes a slightly longer queue rather than a wall of errors. It also makes your logs much easier to read, because you are not mixing real failures with self-inflicted ones.
Watch the quota headers too. If X-Quota-Remaining is falling faster than expected mid-month, you want to know on a Tuesday, not on the morning it reaches zero.
Retrying everything is the most common mistake. Our docs are specific: retry 429, 500 and 503 only. Do not retry other 4xx errors, because they will fail again.
It helps to sort responses into three groups:
Retry after a wait. A 429 from the per-minute limit, and 500 or 503 responses, are usually temporary. Back off and try again.
Do not retry, fix the request. A 400 validation error, a 401 for a bad key, a 404 for a check that does not exist on your account, or a 409 conflict all mean something about the request is wrong. Sending it again unchanged will produce the same answer. Log it, alert on it, and fix the cause. The errors reference lists the codes you will see.
Do not retry today. A daily cap 429 will not clear until midnight UTC. Queue the work for later, or treat it as a sign that the cap no longer matches your volume.
Timeouts sit awkwardly between these. If your client gave up waiting, you do not know whether the server finished the work. That is where idempotency matters, and we come back to it below.
When you do retry, use exponential backoff with jitter, and honour Retry-After when it is present.
Exponential backoff means each retry waits longer than the last: perhaps one second, then two, then four, up to a ceiling. Jitter adds a random element to each wait so that a hundred workers that failed at the same moment do not all retry at the same moment too. Without jitter, you get synchronised waves of retries that keep hitting the limit.
A sketch of the logic:
attempt = 0
loop:
response = call_api()
if response.status in (429, 500, 503) and attempt < max_attempts:
if response has Retry-After:
wait = Retry-After seconds
else:
wait = random(0, base * 2 ** attempt), capped
sleep(wait)
attempt += 1
continue
return response
Set a maximum number of attempts. After that, move the job to a dead-letter queue and alert someone. A check that has failed five times is not going to succeed on the sixth because you were patient.
A retry is only safe if doing the same thing twice has the same effect as doing it once. For read requests, such as fetching a check result or a report, that is naturally true. For requests that create something, such as starting a new verification, it is not automatic.
The risk is easy to picture. Your client sends a KYC check, the server processes it, and the response is lost on the way back. Your client sees a timeout and sends it again. Now there are two checks for one seller, both counted against your allowance, and possibly two different results in your records.
The TrustVerify API does not take an Idempotency-Key header, so build idempotency on your side:
Some operations are idempotent by design. Adding someone to ongoing monitoring who is already enrolled is one: POST /api/v1/aml/monitor returns 200 with the existing monitor and alreadyEnrolled: true, rather than creating a second enrolment or using another monitoring slot. Check other endpoints in the API reference rather than assuming.
Idempotency also applies in the other direction. TrustVerify retries webhook deliveries up to three times with exponential backoff, uses a 10-second timeout and does not follow redirects. That means your endpoint can receive the same event more than once, for example if it processed the event but responded slowly.
Every delivery carries an X-TrustVerify-Delivery-Id header. Store the IDs you have processed and ignore repeats. Respond quickly with a 2xx and do heavy processing afterwards, so you do not trigger retries you did not need. Our planned post on KYC webhooks and decision handling goes into this in more detail. [Planned URL, live on publication.]
The sandbox is the right place to try all of this. Test keys start with tv_test_sk_, sandbox calls do not count towards your monthly allowance, and you can push your worker queue hard enough to see a 429 without affecting live sellers. The sandbox guide explains what test data to use.
Things worth testing on purpose:
X-RateLimit-Remaining reaches zeroRetry-After correctlyX-RateLimit-Remaining and slow down before you hit 429.Retry-After.X-TrustVerify-Delivery-Id.Read the full limits and headers reference, then try it against the sandbox. Open the TrustVerify quickstart.
KYC, KYB, AML screening and fraud prevention in one place, with published pricing.
Developer
KYC Webhooks and Decision Handling for Marketplaces
A practical guide to consuming KYC and fraud webhooks on a marketplace: signatures, retries, idempotency and mapping decisions to seller states.
Developer
Using the TrustVerify Sandbox: Test Keys and Your First Check
A developer guide to the TrustVerify sandbox: test and live keys, deterministic synthetic results, magic test values and what the sandbox does not cover.
Developer
Pulling Verification Reports via API
How to fetch KYC, KYB and AML screening reports by API, check they exist, download the PDF and assemble an evidence bundle for audit.