Reference
Errors and limits
The error envelope, the HTTP statuses VerifyMe returns, a catalogue of every error code, rate limits, payload and upload limits, and how to retry safely.
Error envelope
Every error response from /v1 (and from the applicant and portal APIs) has this shape:
{
"error": {
"code": "IDEMPOTENCY_KEY_REUSED",
"message": "This Idempotency-Key was already used with a different request body.",
"details": [ { "field": "email", "code": "INVALID_EMAIL" } ],
"traceId": "trc_01M3TRRXRX7XZ052JT63"
}
}
| Field | Description |
|---|
code | Stable machine-readable code. Branch on this |
message | Human-readable explanation. May change. Do not parse |
details | Optional array. For validation errors: { field, code } entries, sometimes with extra context such as attemptsLeft |
traceId | Correlation id, equal to the X-Request-Id response header. Quote it to support |
Unhandled problems return 500 VERIFYME_INTERNAL_ERROR with a generic message; internals are never leaked.
HTTP statuses
| Status | Meaning in VerifyMe | Retry? |
|---|
200, 201, 202, 204 | Success. 201 create, 202 accepted for async work (webhook test), 204 delete | n/a |
400 | Validation failed or malformed JSON | No, fix the request |
401 | Missing, invalid, revoked, expired or wrong-environment credential | No |
403 | Authenticated but not permitted: scope, IP allow-list, suspended workspace, document not available | No |
404 | Unknown resource, resource of another workspace, or unknown route | No |
409 | Conflict with current state or idempotency | Only IDEMPOTENCY_IN_PROGRESS and VERIFYME_STATE_CONFLICT (after a short wait) |
410 | Verification link expired (applicant API) | No, issue a new request |
413 | Body or file too large | No |
415 | Wrong content type for an upload | No |
422 | Well-formed but not processable (unknown flow, step rules) | No |
429 | Rate limited | Yes, after Retry-After |
500 | Server error | Yes, with backoff |
503 | Dependency temporarily unavailable | Yes, with backoff |
Error code catalogue
API errors (/v1)
| HTTP | Code | Meaning and what to do |
|---|
| 400 | VERIFYME_VALIDATION_ERROR | One or more fields invalid. Read details[].field and details[].code |
| 400 | VERIFYME_INVALID_JSON | Body is not valid JSON |
| 401 | VERIFYME_UNAUTHENTICATED | Bad or missing Authorization: Bearer ...; revoked or expired key; sandbox key on production or the reverse; bad OAuth client credentials |
| 403 | VERIFYME_INSUFFICIENT_SCOPE | The key lacks the scope named in message |
| 403 | VERIFYME_FORBIDDEN | Source IP not on the key allow-list, or the document is not available (not scanned clean) |
| 403 | VERIFYME_TENANT_SUSPENDED | The workspace cannot create new requests |
| 403 | VERIFYME_URL_EXPIRED | A signed file URL is expired or invalid. Request a new one |
| 404 | VERIFYME_NOT_FOUND | Unknown id, other workspace's id, or unknown API route |
| 409 | IDEMPOTENCY_KEY_REUSED | Same Idempotency-Key, different body |
| 409 | IDEMPOTENCY_IN_PROGRESS | Same key is still being processed. Retry shortly |
| 409 | DUPLICATE_EXTERNAL_REFERENCE | Workspace policy block_active: an active request already exists for this externalReference |
| 409 | RESULT_NOT_READY | Result requested before the applicant submitted |
| 409 | VERIFYME_INVALID_STATE | Action not allowed in the request's current state (cancel a final request, re-issue after submit) |
| 409 | VERIFYME_STATE_CONFLICT | Concurrent modification. Reload and retry |
| 413 | VERIFYME_PAYLOAD_TOO_LARGE | JSON body over the limit (256 KB) |
| 422 | FLOW_NOT_FOUND | Unknown or unpublished flowKey, or no default flow |
| 422 | ENDPOINT_LIMIT | More than 10 webhook endpoints |
| 429 | VERIFYME_RATE_LIMITED | Too many requests. Honour Retry-After |
| 500 | VERIFYME_INTERNAL_ERROR | Unexpected server error |
| 503 | VERIFYME_DEPENDENCY_UNAVAILABLE | Temporary dependency failure |
Validation detail codes
These appear in details[].code of VERIFYME_VALIDATION_ERROR.
| Code | Meaning |
|---|
REQUIRED | A required field is missing or empty |
REQUIRED_OR_INVALID | Idempotency-Key missing or not 8 to 120 allowed characters |
INVALID_EMAIL, INVALID_MOBILE | Format checks |
INVALID_URL | Not an absolute URL, too long, or contains credentials |
UNSAFE_SCHEME | javascript:, data:, vbscript: or file: in a URL |
HTTPS_REQUIRED | HTTP URL in production (returnUrl or webhook URL) |
HOST_NOT_ALLOWED | returnUrl host not on the return URL allow-list |
PRIVATE_ADDRESS_NOT_ALLOWED | Webhook URL resolves to a private or loopback address |
UNKNOWN_EVENT | Event type not in the catalogue |
INVALID_NUMBER, INVALID_VALUE, INVALID | Out of range, not an allowed value, or wrong type |
UNSUPPORTED_GRANT | OAuth grant_type is not client_credentials |
INVALID_SCOPE, ENVIRONMENT_MISMATCH | Key creation in the portal: unknown scope, or key environment differs from the deployment |
Applicant-flow errors
These come from the hosted UI's own API. You see them in the sandbox, in the SDK error event, and in your reviewers' step history; they are not returned by /v1.
| HTTP | Code | Meaning |
|---|
| 401 | VERIFYME_SESSION_EXPIRED | Applicant session expired. Reopen the link |
| 404 | VERIFYME_LINK_INVALID | Link malformed, unknown, re-issued, or used on the wrong domain |
| 410 | VERIFYME_REQUEST_EXPIRED | The request expired |
| 403 | VERIFYME_CSRF | Missing session nonce (UI bug or tampering) |
| 422 | INVALID_OTP, OTP_EXPIRED | Wrong or expired code |
| 429 | OTP_ATTEMPTS_EXCEEDED | Too many wrong codes. Request a new one |
| 422 | PAN_INVALID, BANK_ACCOUNT_INVALID, BANK_NAME_MISMATCH | Provider says the document is invalid, or name mismatch where the flow blocks |
| 422 | DIGILOCKER_NOT_COMPLETED | Consent denied or abandoned |
| 422 | FACE_MISMATCH, FACE_VERIFICATION_FAILED, LIVENESS_FAILED | Biometric checks |
| 422 | UNDERAGE, CONSENT_REQUIRED, GEO_REQUIRED, DOCUMENT_REQUIRED, MAX_FILES_REACHED, STEPS_INCOMPLETE | Flow rules |
| 422 / 413 | FILE_TYPE_NOT_ALLOWED, FILE_TOO_LARGE, DOCUMENT_SCAN_FAILED | Upload rejected |
| 503 | PROVIDER_UNAVAILABLE | A verification provider is down. Progress is saved; retryAfter is set; the attempt is not consumed |
| 409 | STEP_BUSY, STEP_LOCKED, STEP_NOT_AVAILABLE, STEP_ALREADY_COMPLETED, DIGILOCKER_NOT_STARTED, NO_CONSENT | Step ordering and concurrency |
Rate limits
| Scope | Limit | On exceed |
|---|
API key or OAuth token (/v1) | 600 requests per minute per key (deployment default; configurable) | 429 VERIFYME_RATE_LIMITED, Retry-After |
| Failed authentication | 30 failures per 5 minutes per source IP | 401s continue; a high-severity security alert is raised |
| Applicant session | 120 requests per minute per request | 429 |
| Opening links | 40 per minute per IP; 20 unknown tokens per 5 minutes per IP | 404 for bad tokens, alert on enumeration |
| OTP sends | 5 per hour per step, 10 per hour per number per workspace, 30 seconds between sends | 429 with Retry-After |
| OTP verification | 3 wrong attempts per code | OTP_ATTEMPTS_EXCEEDED |
| PAN and bank checks | 10 and 8 attempts per hour per request | 429 |
| Sign-in to the portal | 10 per minute | 429 |
Headers on /v1 responses: X-RateLimit-Limit (per minute), X-RateLimit-Remaining, X-RateLimit-Reset (seconds until the window resets). If you need a higher limit, contact us with your expected peak per minute.
Payload and upload limits
| Item | Limit |
|---|
JSON request body on /v1 | 256 KB (413 VERIFYME_PAYLOAD_TOO_LARGE) |
| OAuth token request | 4 KB |
externalReference | 200 characters |
applicantType | 60 characters |
flowKey | 80 characters |
returnUrl and webhook url | 500 characters |
metadata | 4,000 bytes |
expiresInHours | 1 to 720 |
Idempotency-Key | 8 to 120 characters; retained 24 hours |
| Webhook endpoints per workspace | 10 |
| Allowed origins / return URL hosts | 20 each |
| Steps per flow | 25 |
| Upload size (documents, selfie, geo photos) | 8 MB per file by default (deployment setting) |
| Liveness | 3 to 10 frames, up to 3 MB each |
| Files per document step | 3 by default (maxFiles) |
| File types | JPEG, PNG, WebP; PDF where the step allows it. Detected from content, not file name or extension. PDFs with active content are rejected |
| Signed document URL | valid 120 seconds |
| Webhook delivery timeout | 8 seconds. 8 attempts. Replay window 300 seconds |
Time limits
| Item | Value |
|---|
| Request link lifetime | Default 72 hours; 1 to 720 hours. SUBMITTED and REVIEWING do not expire |
| Applicant session | 2 hours (deployment setting) |
| OAuth access token | 1 hour |
| Mobile OTP validity | 5 minutes. Email OTP 10 minutes |
| Sandbox inbox | 1 hour, latest 30 messages |
| Recent API call log (portal) | latest 100 calls, 24 hours; bodies and credentials are never stored |
Retry guidance
- Always send an
Idempotency-Key on creates and reuse it for every retry of the same logical operation. - Retry on network errors, timeouts,
429, 500, 502, 503 and 504. Use exponential backoff with jitter: for example 0.4 s, 0.8 s, 1.6 s, 3.2 s, each randomised by 20%, and stop after four tries. - On
429, wait at least Retry-After seconds. - On
409 IDEMPOTENCY_IN_PROGRESS or VERIFYME_STATE_CONFLICT, wait a second and repeat. - Do not retry other
4xx responses. They will fail the same way. - A timeout on create does not mean the request was not created. Retrying with the same key returns the original result rather than creating a duplicate.
- For webhooks it is the other side retrying: respond
2xx fast, 5xx for transient problems, and avoid 4xx for anything you want redelivered. See Webhooks. - Circuit-break: if more than half of your calls fail for a minute, pause for 30 to 60 seconds before probing again.
Docs version 1.0.0API version v1Last updated 1 Oct 2026