Skip to content

Error Handling

ProofAge uses conventional HTTP status codes and structured JSON error responses. This guide covers every error format, code, and reason you may encounter so you can build robust error handling into your integration.

Error Response Formats

The API returns errors in three distinct formats depending on where the error originates.

Format 1: Standard API Errors

Returned by authentication middleware and general API errors. The response contains a top-level error object:

json
{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "The provided API key is not valid."
  }
}
FieldTypeDescription
error.codestringMachine-readable error code (see catalog below).
error.messagestringHuman-readable description of the error.

Format 2: Media Validation Errors

Returned when an uploaded media file fails quality or content checks. The error is a flat object (no error wrapper):

json
{
  "code": "FACE_TOO_BLURRY",
  "message": "The uploaded selfie is too blurry. Please upload a clearer image."
}
FieldTypeDescription
codestringMachine-readable validation error code.
messagestringHuman-readable description suitable for display to end users.

Format 3: Laravel Validation Errors

Returned when request input fails validation rules (e.g., missing required fields, wrong types). This follows the standard Laravel validation response format:

json
{
  "message": "The flow field is required.",
  "errors": {
    "flow": [
      "The flow field is required."
    ],
    "mode": [
      "The mode field must be one of: live, test."
    ]
  }
}
FieldTypeDescription
messagestringSummary of the first validation error.
errorsobjectMap of field names to arrays of error messages.

Error Code Catalog

Authentication Errors

These errors are returned by the authentication middleware when a request cannot be authorized.

HTTP StatusCodeDescription
401MISSING_API_KEYThe X-API-Key header was not provided.
401INVALID_API_KEYThe API key does not match any active workspace.
401NO_SECRET_KEYSThe workspace has no secret keys. Generate one in the dashboard.
401MISSING_SIGNATUREThe X-HMAC-Signature header is required but was not provided.
401INVALID_SIGNATUREThe HMAC signature does not match the expected value. Check your secret key and signing logic.

Authorization Errors

HTTP StatusCodeDescription
403WORKSPACE_SUSPENDEDThe workspace has been suspended. Contact support.
403CONSENT_REQUIREDUser consent has not been recorded for this verification. Call the consent endpoint first.

Billing Errors

HTTP StatusCodeDescription
402PAYMENT_METHOD_REQUIREDNo payment method is on file. Add one in the dashboard to use live mode.

Rate Limiting

HTTP StatusCodeDescription
429RATE_LIMITToo many requests. The default limit is 60 requests per minute per API key.

When you receive a 429 response, the Retry-After header indicates how many seconds to wait before retrying:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{
  "error": {
    "code": "RATE_LIMIT",
    "message": "Rate limit exceeded. Try again in 30 seconds."
  }
}

Submission Errors

Returned when attempting to submit a verification that is not in a submittable state.

HTTP StatusCodeDescription
422INVALID_STATUSThe verification is not in a status that allows submission (e.g., already submitted or already in a terminal state).
422MISSING_REQUIRED_MEDIAThe verification is missing required media files. For age flows, a selfie is required. For kyc flows, a selfie and at least one document image are required.

Media Validation Errors

Returned synchronously when an uploaded media file fails quality checks. These use Format 2.

Selfie Validation

HTTP StatusCodeDescription
422FACE_NOT_FOUNDNo face was detected in the uploaded image.
422FACE_INVALID_SIZEThe face is too small or too large relative to the frame.
422FACE_TOO_BLURRYThe image is too blurry for reliable analysis.
422FACE_BRIGHTNESS_INVALIDThe image is too dark or too bright.
422FACE_OCCLUDEDPart of the face is covered (e.g., hand, mask, sunglasses).
422FACE_ALIGNMENT_FAILEDThe face landmarks could not be aligned for processing.
422FACE_TOO_TILTEDThe face is rotated too far from a frontal position.
422FACE_VALIDATION_FAILEDA general face validation failure not covered by the codes above.

Document Validation

HTTP StatusCodeDescription
422DOCUMENT_NOT_FOUNDNo document was detected in the uploaded image.
422DOCUMENT_TOO_BLURRYThe document image is too blurry to read.
422DOCUMENT_BRIGHTNESS_INVALIDThe document image is too dark or too bright.
422DOCUMENT_NOT_READABLEThe document text could not be extracted. Ensure all corners are visible and the image is in focus.
422DOCUMENT_VALIDATION_FAILEDA general document validation failure not covered by the codes above.

HTTP Status Code Summary

Status CodeMeaningTypical Use
200OKSuccessful read or update.
201CreatedResource successfully created (verification, media).
400Bad RequestMalformed JSON or invalid request structure.
401UnauthorizedMissing or invalid API key / signature.
402Payment RequiredNo payment method on file.
403ForbiddenWorkspace suspended or consent not given.
404Not FoundVerification or resource does not exist.
408Request TimeoutThe request took too long to complete.
422Unprocessable EntityValidation failure (input, media, or status).
429Too Many RequestsRate limit exceeded.
500Internal Server ErrorUnexpected server error. Contact support if persistent.

Decision Reason Catalog

When a verification reaches declined or resubmission_requested status, the webhook payload includes a reason field. Reasons are grouped by severity and category, each with an internal priority range that determines which reason takes precedence when multiple issues are detected.

Blocklist & Document Rejection (Priority 100–159) — Declined

These are hard rejections with no option to resubmit.

ReasonPriorityDescription
blocklist_match100Biometrics match a previously blocklisted identity.
not_a_document150The uploaded document image is not a recognized identity document.

Fraud Detection (Priority 200–249) — Declined

Automated fraud signals result in immediate decline.

ReasonPriorityDescription
spoof_detected200Liveness check detected a spoofing attempt (printed photo, screen capture, mask).
document_tampering210Signs of physical or digital alteration on the document.
ai_generated220The submitted image appears to be AI-generated or synthetically manipulated.

Identity & Business Rules (Priority 300–330) — Declined

The verification failed identity matching or business rule checks.

ReasonPriorityDescription
face_mismatch300The selfie does not match the face on the identity document.
age_threshold_not_met310The estimated age is below the required threshold.
duplicate_identity320The same biometric identity has already been verified under a different external ID.

Quality Issues (Priority 400–479) — Resubmission Requested

The user can fix these issues by re-uploading better media.

ReasonPriorityDescription
selfie_too_blurry400The selfie is too blurry for reliable analysis.
selfie_bad_lighting410The selfie is too dark or overexposed.
selfie_face_occluded420Part of the face is covered in the selfie.
selfie_poor_angle425The face is too tilted or not facing the camera.
document_too_blurry440The document image is too blurry to read.
document_bad_lighting450The document image has poor lighting.
document_damaged460The document appears physically damaged or partially obscured.
document_not_readable470The document text could not be extracted.

Technical Failures (Priority 900) — Resubmission Requested

Transient processing errors. Safe to retry without changing the media.

ReasonPriorityDescription
processing_error900An internal error occurred during pipeline processing.

TIP

When multiple issues are detected, only the highest-priority reason (lowest number) is returned. For example, if a selfie is both blurry and a spoofing attempt, the reason will be spoof_detected (priority 200), not selfie_too_blurry (priority 400).


Best Practices

Differentiate Error Formats

Check for the presence of specific keys to determine which error format you received:

javascript
function handleApiError(response) {
  const body = response.data;

  if (body.error && body.error.code) {
    // Format 1: Standard API error
    return { code: body.error.code, message: body.error.message };
  }

  if (body.code) {
    // Format 2: Media validation error
    return { code: body.code, message: body.message };
  }

  if (body.errors) {
    // Format 3: Laravel validation error
    const firstField = Object.keys(body.errors)[0];
    return { code: "VALIDATION_ERROR", message: body.errors[firstField][0] };
  }

  return { code: "UNKNOWN_ERROR", message: "An unexpected error occurred." };
}

Implement Retry Logic

Not all errors are retryable. Follow this decision tree:

StatusAction
401, 403Do not retry. Fix authentication/authorization first.
402Do not retry. Add a payment method in the dashboard.
404Do not retry. The resource does not exist.
408Retry after a brief delay.
422Do not retry with the same payload. Fix the validation issue or prompt the user to upload better media.
429Retry after the number of seconds in the Retry-After header.
5xxRetry with exponential backoff (e.g., 1s, 2s, 4s, up to 30s).

Surface User-Facing Messages

Media validation errors (Format 2) include messages suitable for showing to end users. Use them directly in your UI to guide users toward better uploads:

"The uploaded selfie is too blurry. Please upload a clearer image."

Map Reason Codes to User Actions

When you receive a webhook with resubmission_requested, map the reason to a clear instruction:

ReasonSuggested User Message
selfie_too_blurry"Your selfie was blurry. Please retake it in good lighting."
selfie_bad_lighting"Your selfie was too dark or bright. Try a well-lit area."
selfie_face_occluded"Your face was partially covered. Remove glasses, masks, or hats."
document_too_blurry"Your document photo was blurry. Hold your phone steady."
document_not_readable"We could not read your document. Make sure all four corners are visible."
processing_error"Something went wrong on our end. Please try again."

Log Error Codes

Always log the full error response (code, message, HTTP status) for debugging. Do not discard error details silently.


Next Steps

  • Webhooks — Set up webhook receivers and verify signatures.
  • Testing — Test error scenarios in sandbox mode.
  • Verification Flow — Understand the full verification lifecycle.

ProofAge Developer Documentation