Skip to content
Lira APILira API

Errors

Lira uses consistent error shapes and standard HTTP status codes across all endpoints. This page is the central reference for everything that can go wrong and how to handle it.


Error response format

All errors from Lira follow this standard shape:

JSON
{
  "message": "Human-readable description of the error",
  "code": "MACHINE_READABLE_CODE",
  "statusCode": 422,
  "action": {
    "type": "CONTACT_ADMIN",
    "hint": "Ask your administrator to enable NG Account Number verification in the Lira dashboard."
  }
}
FieldTypeDescription
messagestringA human-readable explanation of the error.
codestringA machine-readable code for programmatic error handling.
statusCodenumberThe HTTP status code, mirrored in the response body for convenience.
actionobjectOptional. Present when the error is recoverable and there is a clear next step. See Actionable errors below.
action.typestringA machine-readable hint type: one of the values in the table below.
action.hintstringA human-readable sentence describing what to do. Safe to display directly to users.


Actionable errors

When action is present in an error response, your application can use action.type to decide how to respond, either routing the user to the right place or surfacing the action.hint text directly.

action.typeMeaningWhat to do
CONTACT_ADMINAn administrator needs to configure something (enable a service, set a price) before this call can succeedSurface action.hint to the user or display a "Contact your admin" prompt
TOP_UP_WALLETThe organization's wallet balance is too low to process the requestRedirect to the billing/wallet page, or surface action.hint
CHECK_API_KEYThe API key is missing, invalid, or revokedPrompt the user to check their API key configuration
FIX_INPUTThe request body contains invalid or missing fieldsSurface the validation details from error.details alongside action.hint
RETRY_LATERA transient upstream or infrastructure issue; retrying will likely succeedRetry with exponential backoff; surface action.hint if retries are exhausted
USE_NEW_KEYThe Idempotency-Key was already used with a different request bodyGenerate a new unique key and retry
WAIT_AND_RETRYA rate or brute-force limit is active; the caller must wait before retryingBack off for the duration shown in Retry-After header or in action.hint

Example: handling action in JavaScript:

JavaScript
const res = await fetch('https://api.lira.com/api/v1/verify/account', { ... });
const body = await res.json();
 
if (!res.ok) {
  if (body.error?.action) {
    switch (body.error.action.type) {
      case 'CONTACT_ADMIN':
        showAdminPrompt(body.error.action.hint);
        break;
      case 'TOP_UP_WALLET':
        redirectToWallet();
        break;
      case 'RETRY_LATER':
      case 'WAIT_AND_RETRY':
        scheduleRetry();
        break;
      default:
        showErrorMessage(body.error.action.hint);
    }
  } else {
    showErrorMessage(body.error.message);
  }
}

HTTP status codes

StatusNameWhen it occurs
400Bad RequestMalformed request body or invalid JSON
401UnauthorizedMissing, invalid, or expired authentication credential
402Payment RequiredInsufficient wallet balance, wallet suspended, or service price not configured
403ForbiddenValid authentication but insufficient role, permissions, or the requested service is not enabled for this organization
404Not FoundThe requested resource (webhook, verification, API key) does not exist
409ConflictIdempotency key conflict, or the same idempotent request is still processing
422Unprocessable EntityThe request is valid JSON but fails validation: missing required field, wrong format, etc.
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error; if persistent, contact support with the request ID

Verification error codes

These codes appear in the error.code field of a verification response when status is failed or error. They are returned with an HTTP 200 OK: the request was processed successfully, but the verification itself did not pass.

CodeVerification typeMeaningRecommended action
ACCOUNT_NOT_FOUNDBank accountAccount number not found at the specified bankAsk the user to check their account number and bank code
INVALID_BANK_CODEBank accountThe bank code is not recognizedCheck the bank code against the supported banks list
INVALID_ACCOUNT_NUMBERBank accountAccount number format is invalidValidate the format before submitting (10 digits for Nigeria)
SUBSCRIBER_NOT_FOUNDPhone numberPhone number is not registered with the carrierAsk the user to verify their number
INVALID_PHONE_NUMBERPhone numberPhone number format is invalidValidate E.164 or local format before submitting
INVALID_NETWORK_CODEPhone numberNetwork code is not recognized for the given countryCheck the supported network codes in Verify a Phone Number
NETWORK_CODE_REQUIREDPhone numberGhana verifications require a networkCodeAdd the networkCode field to the request
PROVIDER_ERRORBothThe upstream registry returned an unexpected errorRetry with exponential backoff
PROVIDER_TIMEOUTBothThe upstream registry did not respond within the timeout windowRetry; this is a transient error

Note

A 200 OK does not mean the verification succeeded. Always check the status field in the response body. A status of failed is a valid, expected outcome. It means the account or number was not found, not that the API call itself failed.


Idempotency error codes

Verification POST endpoints and address verification job creation require an Idempotency-Key header so retries do not create duplicate verifications, jobs, or charges.

CodeHTTP statusMeaningRecommended action
IDEMPOTENCY_KEY_REQUIRED422Missing Idempotency-Key headerGenerate a unique key for this verification or address job request and retry
IDEMPOTENCY_KEY_INVALID422Key is empty, too long, or contains unsupported charactersUse 1-255 printable ASCII characters
IDEMPOTENCY_IN_PROGRESS409The same key is already processingWait briefly, then retry with the same key
IDEMPOTENCY_KEY_CONFLICT409The same key was used with a different request bodyGenerate a new key for the new request body

Webhook error codes

Webhook-related errors are returned as standard HTTP error responses when creating or updating a webhook. The status field on a webhook object reflects its delivery health.

Webhook statusMeaning
activeWebhook is enabled and will receive deliveries
inactiveWebhook has been manually disabled
failedAll retry attempts for the most recent delivery were exhausted
ConditionHTTP statusAction
All retry attempts exhaustedNone (webhook status set to failed)PATCH the webhook status back to active to resume deliveries
Invalid or non-HTTPS URL on create or update422Provide a valid https:// URL that is publicly reachable
Webhook not found404Check the WEBHOOK_ID: use GET /client/webhooks to list all webhooks

Handling errors in your code

The example below shows a verification request that returns a failed result and how to handle it:

Terminal
curl -X POST https://api.lira.com/api/v1/verify/account \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Idempotency-Key: YOUR_IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountNumber": "0000000001",
    "country": "NG",
    "bankCode": "000014"
  }'

Response 200 OK (note: HTTP 200, but verification failed)

JSON
{
  "id": "a1b2c3d4-...-...-...-...",
  "status": "failed",
  "verified": false,
  "error": {
    "code": "ACCOUNT_NOT_FOUND",
    "message": "No account found for the provided details"
  }
}

In your code, always branch on status first, then on error.code:

JavaScript
const result = await response.json();
 
if (result.status === 'success') {
  // proceed: read accountName, bankName, etc.
  console.log('Verified:', result.accountName);
} else if (result.status === 'failed') {
  // handle based on error.code
  switch (result.error.code) {
    case 'ACCOUNT_NOT_FOUND':
      // prompt user to recheck their account number and bank code
      break;
    case 'INVALID_BANK_CODE':
      // show bank code validation error
      break;
    case 'SUBSCRIBER_NOT_FOUND':
      // prompt user to verify their phone number
      break;
    default:
      // unknown failure: log and surface a generic user message
  }
} else if (result.status === 'error') {
  // transient failure, retry with exponential backoff
} else if (result.status === 'pending') {
  // async mode: wait for webhook or poll GET /verify/:id
}

Next steps