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

HTTPErrorCauseFix
401Missing API key. Use Authorization: Bearer {key}No Authorization header or wrong formatAdd Authorization: Bearer YOUR_KEY
401Invalid or inactive API keyKey doesn't exist or has been revokedCheck key value; generate a new key if revoked
401UnauthorizedSession expired or user not authenticatedRe-authenticate

OAuth authorization errors

These appear as redirect parameters: ?error=ERROR_CODE&state=...

errorCauseFix
access_deniedUser declined the consent screenPresent user with option to retry
verification_failedUser abandoned verification or it failedLet user retry — start a new session
session_expired30-minute session TTL elapsedStart a new authorization request
invalid_redirect_uriredirect_uri doesn't match registered valueUse exact registered URL, including protocol and path
invalid_requestMissing or malformed authorization parameterCheck all required parameters are present

OAuth token endpoint errors

HTTPerrorCauseFix
400invalid_requestMissing or malformed parameterCheck all required fields: grant_type, code, client_id, client_secret, redirect_uri, code_verifier
400invalid_grantCode expired (10-min TTL), already used, or code_verifier mismatchGenerate a new authorization request; verify your PKCE implementation
400unsupported_grant_typegrant_type is not authorization_codeSet grant_type=authorization_code
400invalid_scopeRequested scope not permitted for your clientContact CAIRL to enable additional claims
400invalid_redirect_uriredirect_uri doesn't match the value used in the authorization requestEnsure exact match including trailing slash
401invalid_clientWrong client_id or client_secretCheck credentials in your environment variables
402payment_requiredWallet balance insufficientFund your wallet at cairl.app/home/billing ($50 minimum)
403access_deniedEnrollment blocked (e.g., duplicate face detected)Contact CAIRL support
403client_inactivePartner account is not yet activatedComplete account activation
500server_errorInternal errorRetry with exponential backoff; contact support if persistent

Session query errors

HTTPErrorCauseFix
401Missing API keyNo Authorization headerAdd Bearer token
401Invalid or inactive API keyWrong keyCheck key value
403Forbidden: this session belongs to a different API keySession was created by a different keyUse the same key that initiated the session
404Session not found or expiredSession TTL elapsed or ID is wrongThe 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:

CauseFix
Endpoint returns 4xx/5xxFix the handler; deliveries will retry
Signature verification failsCheck that you verify the raw body before parsing JSON
Request times out (>10s)Acknowledge with 200 OK immediately, process asynchronously
Endpoint is unreachableEnsure your server is running and accessible

Rate limits

EndpointLimit
GET /api/verify/hvf-session/{id}60 req/min per API key
POST /api/oauth/token30 req/min per client
Webhook deliveryN/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_description from the response

On this page