> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox & Test Data

> Didit sandbox reference: scenarios, magic values, sample documents, OTP codes and email opt-in. Drive deterministic verification outcomes with no real providers, PII, or billing.

Didit's **sandbox** is the equivalent of Stripe's test cards or Sumsub's document templates: it lets you reproduce any verification outcome deterministically, without touching real providers, real PII, or your balance.

<Note>
  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.
</Note>

## 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 a `mode` 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.

The `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 as `sandbox_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.                                                                                    |
| EMAIL                | `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

<Warning>
  In sandbox the **uploaded image is always ignored**. The OCR provider is mocked, so the extracted document and any warning come entirely from the scenario or magic value on the session — never from the pixels you upload. Never upload real identity documents in sandbox.
</Warning>

These are the user-facing sandbox templates. Sample specimen images for each are maintained in the `service-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`.

By default sandbox sessions do **not** send emails (review alerts, status change, OTP, resubmit, new-session-link, etc.). To deliver an email anyway, use one of these opt-in patterns on the recipient address:

| 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.

```json theme={null}
{
  "workflow_id": "<your-workflow-uuid>",
  "sandbox_scenario": "decline_document_expired"
}
```

### 2. Magic values typed by hand

Set the individual input fields yourself (for example `expected_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:sessions` privilege): `POST /v3/session/{session_id}/simulate/`
* Hosted flow (`Session-Token` header): `POST /v3/session/{session_id}/sandbox/simulate/`

Body fields: `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.

```json theme={null}
{
  "scenario": "decline_aml_hit"
}
```
