Integration
Error Reference
Complete list of CAIRL API errors, their causes, and how to fix them.
Error response format
All CAIRL API errors follow a consistent shape:
{
"success": false,
"error": "Human-readable error message"
}OAuth token endpoint errors follow the OAuth 2.0 error response format:
{
"error": "invalid_grant",
"error_description": "Authorization code has expired or already been used"
}Authentication errors
| HTTP | Error | Cause | Fix |
|---|---|---|---|
401 | Missing API key. Use Authorization: Bearer {key} | No Authorization header or wrong format | Add Authorization: Bearer YOUR_KEY |
401 | Invalid or inactive API key | Key doesn't exist or has been revoked | Check key value; generate a new key if revoked |
401 | Unauthorized | Session expired or user not authenticated | Re-authenticate |
OAuth authorization errors
These appear as redirect parameters: ?error=ERROR_CODE&state=...
error | Cause | Fix |
|---|---|---|
access_denied | User declined the consent screen | Present user with option to retry |
verification_failed | User abandoned verification or it failed | Let user retry — start a new session |
session_expired | 30-minute session TTL elapsed | Start a new authorization request |
invalid_redirect_uri | redirect_uri doesn't match registered value | Use exact registered URL, including protocol and path |
invalid_request | Missing or malformed authorization parameter | Check all required parameters are present |
OAuth token endpoint errors
| HTTP | error | Cause | Fix |
|---|---|---|---|
400 | invalid_request | Missing or malformed parameter | Check all required fields: grant_type, code, client_id, client_secret, redirect_uri, code_verifier |
400 | invalid_grant | Code expired (10-min TTL), already used, or code_verifier mismatch | Generate a new authorization request; verify your PKCE implementation |
400 | unsupported_grant_type | grant_type is not authorization_code | Set grant_type=authorization_code |
400 | invalid_scope | Requested scope not permitted for your client | Contact CAIRL to enable additional claims |
400 | invalid_redirect_uri | redirect_uri doesn't match the value used in the authorization request | Ensure exact match including trailing slash |
401 | invalid_client | Wrong client_id or client_secret | Check credentials in your environment variables |
402 | payment_required | Wallet balance insufficient | Fund your wallet at cairl.app/home/billing ($50 minimum) |
403 | access_denied | Enrollment blocked (e.g., duplicate face detected) | Contact CAIRL support |
403 | client_inactive | Partner account is not yet activated | Complete account activation |
500 | server_error | Internal error | Retry with exponential backoff; contact support if persistent |
Session query errors
| HTTP | Error | Cause | Fix |
|---|---|---|---|
401 | Missing API key | No Authorization header | Add Bearer token |
401 | Invalid or inactive API key | Wrong key | Check key value |
403 | Forbidden: this session belongs to a different API key | Session was created by a different key | Use the same key that initiated the session |
404 | Session not found or expired | Session TTL elapsed or ID is wrong | The 30-min TTL has passed; start a new session |
Webhook delivery errors
Webhook deliveries fail when your endpoint returns a non-2xx status or does not respond within 10 seconds. Failed deliveries are retried automatically (see Webhooks).
Common causes of delivery failure:
| Cause | Fix |
|---|---|
| Endpoint returns 4xx/5xx | Fix the handler; deliveries will retry |
| Signature verification fails | Check that you verify the raw body before parsing JSON |
| Request times out (>10s) | Acknowledge with 200 OK immediately, process asynchronously |
| Endpoint is unreachable | Ensure your server is running and accessible |
Rate limits
| Endpoint | Limit |
|---|---|
GET /api/verify/hvf-session/{id} | 60 req/min per API key |
POST /api/oauth/token | 30 req/min per client |
| Webhook delivery | N/A (outbound, no client-controlled rate) |
Rate-limited requests receive 429 Too Many Requests.
Retrying requests
For 5xx errors, retry with exponential backoff:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 503 && response.status < 500) {
return response; // Don't retry client errors or success
}
if (attempt < maxRetries - 1) {
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000),
);
}
}
throw new Error('Max retries exceeded');
}Do not retry 4xx errors — they indicate a problem with your request that retrying will not fix.
Getting help
If you encounter an error not listed here, or a persistent 5xx error:
- Check your API key is correct and active
- Verify your request matches the expected format
- Contact support with the request timestamp and any
error_descriptionfrom the response