The source of truth for this data is
serialize_catalog() in the verification service, served live at GET /v1/sandbox/scenarios/. A drift-tested copy is generated into docs/sandbox/SANDBOX_REFERENCE.md in the service-didit-verification repo via pipenv run python scripts/generate_sandbox_reference.py. This page mirrors that reference — always trust the live catalog endpoint if anything here looks stale.Environment model
Sandbox is a per-application mode (ApplicationDetail.mode == SANDBOX), the same idea as Stripe test mode. While a session runs under a sandbox application:
- Every external provider (OCR, AML, face match, liveness, NFC, phone, email, IP, POA, database validation, document AI) is mocked — no third party is ever called.
- Sessions are never billed and the balance check is bypassed.
- A per-application quota of 500 session creates / 24h is enforced.
- Webhooks carry
"environment": "sandbox"(live sessions carry"environment": "live"). - The hosted flow exposes the session environment so the UI can warn users not to upload real PII.
- The uploaded image is ignored: outcomes come from the scenario / magic value, not the pixels.
How to switch between live and sandbox
Live and sandbox are separate applications, so test traffic and production data never mix. Each application has amode of live or sandbox:
- Use a sandbox-mode application’s API key to get mocked, unbilled, simulatable sessions.
- Use a live-mode application’s API key for real verifications.
sandbox_scenario field on session create and the simulate endpoints are only accepted on sandbox applications; they are rejected on live applications.
Scenarios
Each scenario is a named bundle of magic values. Pass its slug assandbox_scenario on session create (or to the simulate endpoint) to reproduce the outcome.
| Slug | Title | Default status | Description |
|---|---|---|---|
approve | Happy path — all features approve | Approved | Default sandbox behaviour: every mocked feature returns its clean APPROVED outcome. Session status ends as APPROVED. |
decline_aml_hit | AML — politically exposed person | Declined | AML mock returns one hit and the session declines on the AML feature. Decision payload carries a realistic AML row (status=DECLINED, total_hits>=1) and a POSSIBLE_MATCH_FOUND warning Log. |
decline_document_expired | OCR — document expired | Declined | OCR mock returns a passport whose expiry date is in the past. The session declines on ID verification with a DOCUMENT_EXPIRED warning Log. |
decline_minimum_age | OCR — under the minimum age policy | Declined | OCR mock returns a DOB making the user roughly 10 years old. Production age check emits MINIMUM_AGE_NOT_MET and the workflow declines. |
decline_mrz_validation | OCR — MRZ checksum failure | Declined | OCR mock returns a passport with a corrupted MRZ checksum. Production MRZ validator emits MRZ_VALIDATION_FAILED and the session declines. |
decline_could_not_recognize_document | OCR — unreadable document | Declined | OCR mock returns an empty response. Production raises COULD_NOT_RECOGNIZE_DOCUMENT and surfaces it to the SDK. |
decline_face_match_low_similarity | Face match — similarity below decline threshold | Declined | Face match mock returns a low score. Production emits LOW_FACE_MATCH_SIMILARITY and the workflow node action mapping declines. |
decline_database_no_match | Database validation — no match across all services | Declined | Every selected database service returns NO_MATCH. Production emits DATABASE_VALIDATION_NO_MATCH and the workflow declines. |
decline_phone_high_risk | Phone — Twilio reserved high-risk number | Declined | Prelude mock blocks the verification (Twilio reserved range). Phone verification ends as BLOCKED and emits HIGH_RISK_PHONE_NUMBER. |
decline_email_breach | Email — HIBP breach hit | Declined | HIBP mock returns one breach hit on the address. Email verification emits BREACHED_EMAIL_DETECTED. |
decline_ip_blocklist | IP analysis — IP address in blocklist | Declined | IP analysis mock declines the location with IP_ADDRESS_IN_BLOCKLIST. Session declines on IP analysis. |
decline_poa_address_mismatch | POA — address mismatch with provided | Declined | POA mock declines the proof-of-address with ADDRESS_MISMATCH_WITH_PROVIDED. Session declines on POA. |
decline_liveness_attack | Liveness — face attack detected | Declined | Passive liveness mock detects a face-attack signal and emits LIVENESS_FACE_ATTACK. Session declines on liveness. |
decline_nfc_chip_not_verified | NFC — ePassport chip not verified | Declined | ePassport mock returns a chip whose SOD/DG signature fails integrity. Production emits NFC_CHIP_NOT_VERIFIED and the session declines on NFC when the workflow declines on unverified chips. |
decline_document_ai_unreadable | Document AI — unreadable document | Declined | Document AI mock returns no readable fields. Production emits DOCUMENT_AI_UNREADABLE_DOCUMENT and the document settles as declined once the retry budget is exhausted. |
Magic values
A magic value is a specific input that makes one mocked feature emit a specific risk. Set the input field to the magic value (directly, or via a scenario) to trigger it.| Feature | Input field | Magic value | Risk | Description |
|---|---|---|---|---|
| AML | user_email | sandbox+aml_hit@didit.test | POSSIBLE_MATCH_FOUND | AML — politically exposed person; AML mock returns one hit, session declines on AML. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_DOCUMENT_EXPIRED | DOCUMENT_EXPIRED | OCR — document expired; session declines on ID verification. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_MINIMUM_AGE_NOT_MET | MINIMUM_AGE_NOT_MET | OCR — date of birth makes the user below the configured minimum age; session declines. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_MAXIMUM_AGE_EXCEEDED | MAXIMUM_AGE_EXCEEDED | OCR — extracted DOB exceeds the configured maximum age policy. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_DOCUMENT_NUMBER_FORMAT_MISMATCH | DOCUMENT_NUMBER_FORMAT_MISMATCH | OCR — extracted document number does not match the document’s expected format regex from the document registry. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_PERSONAL_NUMBER_FORMAT_MISMATCH | PERSONAL_NUMBER_FORMAT_MISMATCH | OCR — extracted personal number fails the document registry’s format check. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION | DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION | OCR — extracted document type is outside the application’s documents_allowed policy. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_FULL_NAME_MISMATCH_WITH_PROVIDED | FULL_NAME_MISMATCH_WITH_PROVIDED | OCR — name on the document doesn’t match the name the customer prefilled in expected_details above the name-match threshold. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_GENDER_MISMATCH_WITH_PROVIDED | GENDER_MISMATCH_WITH_PROVIDED | OCR — extracted gender disagrees with expected_details.gender. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_DOB_MISMATCH_WITH_PROVIDED | DOB_MISMATCH_WITH_PROVIDED | OCR — extracted date of birth disagrees with expected_details.date_of_birth. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_COUNTRY_MISMATCH_WITH_PROVIDED | COUNTRY_MISMATCH_WITH_PROVIDED | OCR — issuing state disagrees with expected_details.country (or id_country). |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_NATIONALITY_MISMATCH_WITH_PROVIDED | NATIONALITY_MISMATCH_WITH_PROVIDED | OCR — extracted nationality disagrees with expected_details.nationality. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_IDENTIFICATION_NUMBER_MISMATCH_WITH_PROVIDED | IDENTIFICATION_NUMBER_MISMATCH_WITH_PROVIDED | OCR — extracted document/personal/tax number doesn’t match expected_details.identification_number. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_COULD_NOT_RECOGNIZE_DOCUMENT | COULD_NOT_RECOGNIZE_DOCUMENT | OCR pipeline returns empty / unrecognizable; production raises COULD_NOT_RECOGNIZE_DOCUMENT. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_MRZ_NOT_DETECTED | MRZ_NOT_DETECTED | OCR — document expects an MRZ but the pipeline could not locate one. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_MRZ_VALIDATION_FAILED | MRZ_VALIDATION_FAILED | OCR — MRZ was found but failed checksum / format validation. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME | MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME | OCR — MRZ identity fields disagree with VIZ-extracted fields. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_BARCODE_NOT_DETECTED | BARCODE_NOT_DETECTED | OCR — document expects a barcode but none was found. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_BARCODE_VALIDATION_FAILED | BARCODE_VALIDATION_FAILED | OCR — barcode was decoded but failed cross-field validation. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_QR_NOT_DETECTED | QR_NOT_DETECTED | OCR — document expects a QR code but none was found. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_QR_VALIDATION_FAILED | QR_VALIDATION_FAILED | OCR — QR code was decoded but failed cross-field validation. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_DATA_INCONSISTENT | DATA_INCONSISTENT | OCR — internal consistency checks on VIZ fields disagree. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_DOCUMENT_SIDES_MISMATCH | DOCUMENT_SIDES_MISMATCH | OCR — front and back document_name do not pair as the same document. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_NAME_NOT_DETECTED | NAME_NOT_DETECTED | OCR — neither first nor last name could be extracted from the document. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_DATE_OF_BIRTH_NOT_DETECTED | DATE_OF_BIRTH_NOT_DETECTED | OCR — document class expects a DOB but extraction returned none. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_EXPIRATION_DATE_NOT_DETECTED | EXPIRATION_DATE_NOT_DETECTED | OCR — document class expects an expiry date but extraction returned none. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_DOCUMENT_NUMBER_NOT_DETECTED | DOCUMENT_NUMBER_NOT_DETECTED | OCR — neither document number nor personal number could be extracted. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_COULD_NOT_DETECT_DOCUMENT_TYPE | COULD_NOT_DETECT_DOCUMENT_TYPE | OCR — classifier could not map the capture to a supported document type. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_PORTRAIT_IMAGE_NOT_DETECTED | PORTRAIT_IMAGE_NOT_DETECTED | OCR — pipeline could not isolate a portrait region on the document. |
| ID_VERIFICATION | expected_details.first_name | SANDBOX_OCR_UNPARSED_ADDRESS | UNPARSED_ADDRESS | OCR — document has an address field but geocoding / parsing returned nothing usable. |
| DATABASE_VALIDATION | expected_details.first_name | SANDBOX_DB_NO_MATCH | DATABASE_VALIDATION_NO_MATCH | Database validation — every government/registry service returns NO_MATCH; session declines on database validation. |
| FACEMATCH | expected_details.first_name | SANDBOX_FACE_LOW_MATCH | LOW_FACE_MATCH_SIMILARITY | Face match — similarity score below the decline threshold; session declines on face match. |
| PHONE | phone_number | +15005550009 | HIGH_RISK_PHONE_NUMBER | Phone — Twilio-style undeliverable / high-risk number; phone verification mock returns failure. |
user_email | sandbox+breach@didit.test | BREACHED_EMAIL_DETECTED | Email — HIBP returns a breach hit on this address. | |
| LOCATION | expected_details.first_name | SANDBOX_IP_BLOCKLIST | IP_ADDRESS_IN_BLOCKLIST | IP analysis — caller IP appears in the application blocklist; session declines on IP analysis. |
| PROOF_OF_ADDRESS | expected_details.first_name | SANDBOX_POA_ADDRESS_MISMATCH | ADDRESS_MISMATCH_WITH_PROVIDED | POA — address on uploaded document disagrees with the address the customer prefilled in expected_details; session declines on POA. |
| LIVENESS | expected_details.first_name | SANDBOX_LIVENESS_ATTACK | LIVENESS_FACE_ATTACK | Liveness — passive liveness mock detects a face-attack signal; session declines on liveness. |
| NFC | expected_details.first_name | SANDBOX_NFC_CHIP_NOT_VERIFIED | NFC_CHIP_NOT_VERIFIED | NFC — ePassport mock returns a chip whose SOD/DG signature fails integrity; production emits NFC_CHIP_NOT_VERIFIED and declines when the workflow declines on unverified chips. |
| DOCUMENT_AI | expected_details.first_name | SANDBOX_DOCUMENT_AI_UNREADABLE | DOCUMENT_AI_UNREADABLE_DOCUMENT | Document AI — extraction returns no readable fields; production emits DOCUMENT_AI_UNREADABLE_DOCUMENT and the document settles as declined once the retry budget is exhausted. |
The 10 sample documents
These are the user-facing sandbox templates. Sample specimen images for each are maintained in theservice-didit-verification repo under docs/sandbox/sample-documents/<slug>.png (added separately; they may not all be present yet).
| Document | How it’s surfaced |
|---|---|
| Brazil - ePassport (2023) #1 | Default OCR render — returned when no ID-verification magic value is set (e.g. the approve scenario). Also the base for most SANDBOX_OCR_* magic values (expired, age, name/DOB/gender mismatch, MRZ, portrait, etc.). |
| Pakistan - Passport (2017) | SANDBOX_OCR_BARCODE_NOT_DETECTED, SANDBOX_OCR_BARCODE_VALIDATION_FAILED, SANDBOX_OCR_MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME |
| Iraq - Passport (2017) | Reference template for document pickers (Arabic script); default render is the Brazil ePassport. |
| China - Id Card (2004) | Reference template for document pickers (Chinese script, front + back). |
| Israel - ePassport (2013) | Reference template for document pickers (Hebrew script). |
| China - Residence Permit #3 | Reference template for document pickers. |
| Australia - Victoria Driving License (2009-2022) | SANDBOX_OCR_UNPARSED_ADDRESS |
| Costa Rica - Id Card (2019) | SANDBOX_OCR_DATA_INCONSISTENT (front + back) |
| Argentina - Id Card (2009) | SANDBOX_OCR_DOCUMENT_SIDES_MISMATCH (front + back) |
| Bahamas - Driving License #1 | Reference template for document pickers. |
OTP codes and email opt-in
- Phone OTP: the sandbox phone check accepts only the magic code
123456. - Email OTP: the sandbox email check accepts only the magic code
123456.
| Opt-in pattern | Example |
|---|---|
<local>+sandbox@<domain> | alice+sandbox@example.com |
*@didit.test | alice@didit.test |
Driving outcomes: scenarios, magic values, and the simulate endpoint
There are three ways to control a sandbox session’s outcome.1. sandbox_scenario at session create
Set the scenario slug when creating the session (POST /v3/session/). The create flow expands the scenario’s bundled magic values into the session’s input fields, so the mocked providers emit the intended outcome through the real workflow pipeline.
2. Magic values typed by hand
Set the individual input fields yourself (for exampleexpected_details.first_name = "SANDBOX_OCR_DOCUMENT_EXPIRED", or user_email = "sandbox+aml_hit@didit.test"). See the magic values table above for the full registry.
3. The simulate endpoint
Force a terminal status after the fact:- Console / API key (requires the
write:sessionsprivilege):POST /v3/session/{session_id}/simulate/ - Hosted flow (
Session-Tokenheader):POST /v3/session/{session_id}/sandbox/simulate/
new_status, scenario, risk, comment, node_id — all optional, but at least one of new_status, scenario, or risk is required. When only scenario or risk is given, new_status defaults to that scenario’s / risk’s default status, and the matching warning Log is written.