Reason codes
Every EzVerify API response carries a machine-readable reason code. Route your integration logic on error.code — never on the free-text message, which may change. Always log the request_id: support needs it to trace a request.
{
"ok": false,
"error": {
"code": "MASK_NO_AADHAAR_FOUND",
"message": "human-readable detail"
},
"request_id": "req_..."
}
{
"ok": true,
"data": { ... },
"request_id": "req_...",
"took_ms": 123
}
Entries under Response fields are not errors — they are decision fields returned inside data on successful (HTTP 200) responses.
Platform & auth
Codes returned by the platform (console, key management, billing) and the shared auth/limits layer in front of every verification service.
| Code | HTTP | Services | Meaning | What to do |
|---|---|---|---|---|
AUTH_INVALID_KEY | 401 | All verification APIs | Missing or invalid X-API-Key header. | Send a valid, active API key in the X-API-Key header; mint/rotate keys in the console. |
INVALID_KEY | 401 | Platform | API key is unknown or revoked. | Use an active key; rotate/mint a new one in the console if revoked. |
RATE_LIMITED | 429 | All verification APIs | Per-client requests-per-minute limit exceeded; a Retry-After header is set. | Back off and retry after the Retry-After interval; upgrade your plan for higher limits. |
NOT_FOUND | 404 | All | Route does not exist, async jobs are disabled, the job_id is unknown/expired (job results expire after their TTL), or a referenced account/plan does not exist. | Check the URL; poll jobs before they expire; use the synchronous endpoint if async jobs are disabled. |
METHOD_NOT_ALLOWED | 405 | All verification APIs | HTTP method not supported on this route (e.g. GET on a POST endpoint). | Use the documented HTTP method (POST for all processing endpoints). |
PAYLOAD_TOO_LARGE | 413 | All verification APIs | Request body exceeded the allowed size. | Reduce the upload size (compress/downscale) and retry. |
VALIDATION_ERROR | 422 | All verification APIs | Request body/parameters failed validation (missing multipart file, invalid base64 in file_b64, blank name/address, malformed JSON, etc.). | Fix the request shape per the API reference (correct field names, valid base64, non-blank inputs) and retry. |
INTERNAL | 500 | All | Unexpected server-side failure. Also returned with HTTP 503 on /v1/aml/screen when the sanctions snapshot is not provisioned. | Retry; if persistent, contact support with the request_id. |
UNAUTHORIZED | 401 | Console | Missing or invalid/expired Bearer JWT — login required. | Log in via POST /v1/auth/login and send Authorization: Bearer <token>. |
BAD_CREDENTIALS | 401 | Console | Invalid email or password. | Check credentials or use POST /v1/auth/forgot to reset. |
SIGNUP_DISABLED | 403 | Console | Self-serve signup is off; businesses onboard via KYB verification. | Contact onboarding@ezfinanz.com with MCA/GST/PAN details for verified onboarding. |
EMAIL_TAKEN | 409 | Console | The email is already registered. | Log in with the existing account or use password reset. |
INVALID_TOKEN | 400 | Console | Password-reset link/token is invalid or expired. | Request a new reset link via POST /v1/auth/forgot. |
USER_NOT_FOUND | 404 | Console | The user referenced by the reset token no longer exists. | Request a fresh reset link; contact support if the account was removed. |
WEAK_PASSWORD | 422 | Console | New password shorter than 8 characters. | Choose a password of at least 8 characters. |
UNKNOWN_SERVICE | 400 | Console | Requested service is not one of aadhaar-mask, id-ocr, match-engine, face-verify. | Subscribe using one of the listed service names. |
APP_NOT_FOUND | 404 | Console | App id does not exist or is not owned by your account. | Use an app_id from GET /v1/apps for your own account. |
KEY_NOT_FOUND | 404 | Console | Key id does not exist or belongs to another account. | Use a key id listed under your own apps in GET /v1/apps. |
ACCOUNT_NOT_VERIFIED | 403 | Console | Business is not KYB-verified yet; live API keys are only issued to verified accounts. | Complete business verification (MCA/GST/PAN) with the platform team, then mint keys. |
ACCOUNT_SUSPENDED | 403 | Platform | The account is suspended; all its API keys stop validating immediately. | Contact EzVerify support/billing to resolve the suspension and reactivate. |
SERVICE_NOT_ENABLED | 403 | Platform | Your key is valid but the app is not subscribed to the requested service. | Subscribe the app via POST /v1/apps/{app_id}/services with the service name. |
Aadhaar Mask & OCR
Codes from the document service: masking, detection, extraction (Aadhaar/PAN/passport/Voter ID/DL), document classification, Secure QR verification, and async jobs.
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
MASK_INGEST_ERROR | 422 | Generic document-ingestion failure (parent of the more specific MASK_* ingest errors). | Re-submit a valid JPG/PNG/PDF document. |
MASK_FILE_TOO_LARGE | 413 | Uploaded file exceeds the 15 MB limit (or the async job input cap). | Compress or downscale the file below 15 MB and re-submit. |
MASK_UNSUPPORTED_TYPE | 415 | Content-Type is not multipart/form-data or application/json, or the file is not a supported image/PDF format. | Send multipart/form-data with a file field or JSON with file_b64; upload JPG/PNG/PDF only. |
MASK_INVALID_DOCUMENT | 422 | The document could not be decoded/processed (corrupt or truncated image/PDF page). | Re-submit a valid, uncorrupted document (re-scan/re-photograph if needed). |
MASK_TOO_MANY_PAGES | 422 | PDF exceeds the 10-page limit. | Split the PDF and submit up to 10 pages per request. |
MASK_INCOMPLETE | 422 | An Aadhaar was detected but its number could not be masked on one or more pages; the request is rejected rather than returning an unmasked Aadhaar (fail-closed policy). | Re-submit a clearer, higher-resolution image of the card. |
MASK_NO_AADHAAR_FOUND | 422 | No valid Aadhaar number was found in the document (only raised when fail_if_none=true; otherwise the original file is returned unmodified). | Confirm the upload is an Aadhaar; send a sharper image, or omit fail_if_none to receive the file back unchanged. |
ID_TYPE_NOT_SUPPORTED | 422 | The requested id_type is not supported on this endpoint (mask/detect/process/jobs are Aadhaar-only; extract supports aadhaar, pan, passport, voter, dl). | Use a supported id_type, or the dedicated /v1/<doc>/extract route for that document. |
CONSENT_REQUIRED | 400 | A consent field was supplied but is not a positive value (yes/true/y/1). | Capture the data principal's consent and send consent=yes (or omit the field if consent is recorded upstream). |
EXTRACT_NOT_AADHAAR | 422 | No Aadhaar document was detected in the upload. | Verify the correct document was uploaded; use /v1/doc/classify to identify the document type first. |
EXTRACT_NOT_PAN | 422 | No PAN card was detected in the upload. | Upload a clear image of a PAN card, or classify the doc first via /v1/doc/classify. |
EXTRACT_NOT_PASSPORT | 422 | No passport was detected in the upload. | Upload the passport bio page (MRZ visible) and retry. |
EXTRACT_NOT_VOTER | 422 | No Voter ID (EPIC) was detected in the upload. | Upload a clear Voter ID image and retry. |
EXTRACT_NOT_DL | 422 | No Driving Licence was detected in the upload. | Upload a clear Driving Licence image and retry. |
QR_NOT_FOUND | 422 | No decodable QR code found on the document (the Secure QR is dense and needs resolution). | Send a sharper/higher-resolution scan (QR region ≥ ~400 px wide) or pass the scanner output as qr_text. |
QR_NOT_AADHAAR | 422 | A QR was decoded but it is neither a UIDAI Secure QR big-integer nor legacy Aadhaar XML. | Verify the document carries an Aadhaar QR; re-scan the correct card. |
QR_PARSE_ERROR | 422 | The Aadhaar QR payload is malformed/truncated and could not be parsed. | Re-scan the QR at higher resolution, or pass a complete qr_text payload. |
CERT_PARSE | 500 | The bundled UIDAI public certificate could not be parsed server-side (a deployment issue, not a problem with your input). | Contact support with the request_id — this is a server-side certificate provisioning problem. |
JOBS_UNAVAILABLE | 503 | The async job store is unavailable. | Use the synchronous endpoint (/v1/aadhaar/mask or /v1/aadhaar/process) or retry later. |
Match Engine & AML
The match engine has no service-specific error codes: name/address/person matching and AML screening return only the shared codes from Platform & auth. One shared code has an AML-specific variant:
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
INTERNAL | 503 | On /v1/aml/screen only: the sanctions snapshot is not provisioned on the server. | Retry later; if persistent, contact support with the request_id. |
Match and AML results (decision, band, match_found) are decision fields, not errors — see Response fields.
Face Verify
Codes from face compare, liveness, hosted capture sessions, active-liveness challenges, and 1:N enroll/search.
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
BAD_IMAGE | 422 | One or more supplied base64 images could not be decoded into an image. | Send valid base64-encoded JPG/PNG bytes in the *_b64 fields. |
FACE_NOT_FOUND | 422 | No face was detected in the supplied image. | Re-capture with the full face visible, well-lit, and centered. |
FACE_MULTIPLE | 422 | More than one face detected where exactly one is required. | Re-capture with only the subject's face in frame. |
FACE_TOO_SMALL | 422 | Detected face is below the minimum pixel size for reliable matching (strict live-selfie paths). | Move the camera closer / capture at higher resolution. |
IMAGE_BLURRY | 422 | Image sharpness is below the blur threshold for a live selfie. | Hold the camera steady and re-capture in better light. |
SESSION_NOT_FOUND | 404 | Unknown or expired hosted-verification session id. | Create a new session via POST /v1/face/session and reissue the capture link. |
SESSION_EXPIRED | 410 | The capture link/session has expired or never existed. | Issue a fresh session/capture link to the end user. |
PHOTO_NOT_FOUND | 404 | No selfie has been captured for this session yet. | Wait until the end user completes the capture flow (session status decided) before fetching the photo. |
NO_ACTIVE_CHALLENGE | 404 / 409 | The session has no active-liveness challenge issued (session not created with active:true). | POST /v1/face/session/{sid}/challenge to issue one, or create the session with active:true. |
CHALLENGE_ALREADY_USED | 409 | The one-shot active-liveness challenge was already consumed by a prior submission. | Rotate the challenge (POST .../challenge) to get a fresh one, then resubmit. |
CHALLENGE_EXPIRED | 410 | The active-liveness challenge expired before (or during) submission. | Request a new challenge and complete it within its expiry window. |
CHALLENGE_NONCE_MISMATCH | 403 | Submitted nonce does not match the issued challenge (does not consume the challenge). | Submit with the nonce returned by the challenge issue/fetch call. |
FACE_ID_NOT_FOUND | 404 | No enrolled face with that id in your account's namespace. | Verify the face_id belongs to your account (from your enroll response). |
Response-field reason codes
These are not errors. They arrive inside data on successful (HTTP 200) responses and tell you what the service decided. Route your workflow on these fields.
Aadhaar Mask & OCR
| Field / values | Where | Meaning | What to do |
|---|---|---|---|
status: queued | running | done | failed | GET /v1/jobs/{job_id} | Async job lifecycle state. A failed job carries data.error = {code, message} with the same codes as the sync endpoints. | Poll until done/failed; on failed, act on the embedded error.code exactly as for the sync endpoint. |
doc_type: aadhaar | pan | passport | voter | dl | unknown | /v1/doc/classify | Document classification verdict; unknown when confidence < 0.3, low_confidence flags a degraded capture; secondary_types lists other IDs found in a collage. | Route the doc to the matching extract endpoint; on unknown/low_confidence, request a clearer capture. |
signature_valid: true | false | null | /v1/aadhaar/verify-qr | Offline UIDAI signature verdict for the Secure QR: true = cryptographically genuine, false = tampered/forged (still HTTP 200), null = legacy XML QR (not signature-verifiable). | Treat false as a forged/tampered card — reject or escalate; treat null as unverifiable and use other checks. |
Match Engine & AML
| Field / values | Where | Meaning | What to do |
|---|---|---|---|
decision: ACCEPT | REVIEW | REJECT | /v1/match/name, /v1/match/address, /v1/match/person | The one field to route on: ACCEPT = auto-pass, REVIEW = human queue, REJECT = auto-fail; p_match is the calibrated probability (0–100) the two records are the same person. | Auto-accept on ACCEPT, auto-fail on REJECT, queue REVIEW for a human checker. |
band: AUTO_MATCH | REVIEW | NO_MATCH | Same endpoints | Score band (AUTO_MATCH ≥ 90, REVIEW 75–89, NO_MATCH < 75; tunable). Accompanied by reasons[] / fields explainability strings. | Use band (or decision) for routing; inspect reasons/fields when auditing a REVIEW. |
match_found: true | false | /v1/aml/screen | Whether any sanctions-list entry (UN SC Consolidated + OFAC SDN) scored at or above the requested threshold; matches[] carries list, uid, score, matched name, programs. | On true, route to human AML review — a hit is a name-similarity signal, not a legal determination. |
dob_conflict: true | false | /v1/aml/screen (per match, when a DOB was supplied) | All DOBs listed for the matched sanctions entry clearly contradict the supplied DOB (> 2-year tolerance) — a de-prioritisation signal for the reviewer, never an auto-drop. | Reviewers may down-weight the hit but should not auto-clear on this flag alone. |
Face Verify
| Field / values | Where | Meaning | What to do |
|---|---|---|---|
verdict: MATCH | REVIEW | NO_MATCH | /v1/face/compare, kyc-check.face, liveness-selfie.face_match | 1:1 face-match verdict from similarity vs thresholds; a NO_MATCH against a low-quality reference photo is downgraded to REVIEW with reference_low_quality=true. | Auto-pass MATCH, auto-fail NO_MATCH, queue REVIEW; on reference_low_quality, supply a better reference photo. |
band: high | medium | review | low | Same responses | Confidence band alongside the verdict: high = well above match threshold, medium = at threshold, review/low = below. | Optionally apply stricter downstream policy to medium matches than high. |
verdict (liveness): LIVE | REVIEW | SPOOF | /v1/face/liveness, kyc-check.liveness, /v1/face/liveness-selfie | Liveness decision: LIVE = passed; REVIEW = low passive score, failed challenge, model not provisioned, or accessory/injection flags; SPOOF = hard reject. | Step-up (retry or manual review) on REVIEW; reject on SPOOF; check the accompanying reason/flags fields for why. |
reason (liveness): liveness_model_not_provisioned | face-error code | /v1/face/liveness, kyc-check.liveness | Why liveness could not be scored: the passive anti-spoof model is absent (verdict REVIEW, never a faked LIVE) or the face failed quality gates (e.g. FACE_NOT_FOUND, verdict SPOOF). | liveness_model_not_provisioned is a server config state — rely on the active challenge or contact support; face-error reasons need a recapture. |
decision (KYC): PASS | REVIEW (webhook also FAILED) | /v1/face/kyc-check, liveness-selfie.kyc_decision, session webhook | Composite KYC outcome: PASS only when face MATCH + liveness LIVE (if required) + geo in India; anything else REVIEW; the webhook sends FAILED when the selfie raised a face error. | Complete onboarding on PASS; route REVIEW to a checker; on FAILED, have the user retry the capture. |
active_live: true | false | POST .../active-liveness; GET /v1/face/session/{sid} | Server-verified active-liveness outcome (frame order, same-face, geometry, timing); per-frame details may carry face-error codes or UNKNOWN_CHALLENGE for an unrecognised challenge name. | On false, rotate the challenge and have the user redo the sequence. |
injection_flags: virtual_camera_label | uniform_frame_timing | mock_location | /v1/face/liveness-selfie (also in webhook) | Advisory stream-injection heuristics (virtual camera software, impossibly uniform frame cadence, mocked GPS); any flag forces verdict REVIEW, never auto-reject. | Treat flagged sessions as higher risk; ask the user to retry from a real device camera. |
retake_reasons / accessories.detected: glasses | sunglasses | cap | mask | /v1/face/liveness-selfie; POST .../accessory-hint | Accessory detected on the final selfie; by default any accessory downgrades a LIVE verdict to REVIEW. | Instruct the user to remove the listed accessory and retake the selfie. |
status (session): created | opened | capturing | decided | GET /v1/face/session/{sid}; webhook | Hosted capture-session state machine; decided means a result (or error) is available; binding_violations counts link-forwarding to a second device. | Poll or take the webhook; fetch data.result once status is decided. |
No reason codes match your filter.