Reference for kycert API HTTP status codes and error objects. Understand what each error means and how to handle authentication failures, rate limits, and validation errors.
The kycert API returns standard HTTP status codes alongside a consistent JSON error body. Every error response — from authentication failures to validation issues — follows the same envelope structure, so you can write generic error-handling logic and then branch on the code field for specific conditions.
The kycert API enforces per-key rate limits. When you exceed the limit, the API returns 429 Too Many Requests. Every API response includes rate limit headers:
Header
Description
X-RateLimit-Limit
Maximum requests allowed per minute for this key
X-RateLimit-Remaining
Requests remaining in the current window
X-RateLimit-Reset
Unix timestamp when the current window resets
Retry-After
Seconds to wait before retrying (present on 429 responses)
Use the Idempotency-Key header on POST /bureau/runs to make retries safe. If you send the same Idempotency-Key value within 24 hours, the API returns the original response without creating a duplicate run.kycert stores the idempotency key as a keyed hash scoped to your tenant, with a 24-hour TTL. This means the same key value used by two different tenants does not conflict.
Use a UUID v4 or any sufficiently random string as the key value. Tie it to the specific operation in your system — for example, derive it from your internal customer ID and the operation type — so retries after a network failure always send the same key.
Without an Idempotency-Key, every retry of a POST /bureau/runs request creates a new run and consumes credits. Always include an idempotency key when retrying failed or timed-out requests.
Apply this pattern for all kycert API calls. Respect Retry-After on 429 responses, use exponential backoff on 5xx errors, and never retry 4xx errors other than 429 — they indicate a problem with the request itself that will not resolve on its own.
async function fetchWithRetry( url: string, options: RequestInit, maxRetries = 3,): Promise<Response> { for (let attempt = 0; attempt < maxRetries; attempt++) { const res = await fetch(url, options) // Success if (res.ok || (res.status >= 200 && res.status < 300)) return res // 429: respect the Retry-After header if (res.status === 429) { const retryAfter = parseInt(res.headers.get('retry-after') ?? '60', 10) await new Promise(r => setTimeout(r, retryAfter * 1000)) continue } // 5xx: exponential backoff if (res.status >= 500) { if (attempt < maxRetries - 1) { await new Promise(r => setTimeout(r, 1000 * 2 ** attempt)) continue } return res } // 4xx (except 429): do not retry — fix the request return res } throw new Error('Max retries reached')}
Status
Retry?
Wait
4xx (except 429)
No
Fix the request
429
Yes
Value of Retry-After header
5xx
Yes
Exponential backoff: 1s, 2s, 4s, …
Always pair retries on POST /bureau/runs with an Idempotency-Key to prevent duplicate runs.