Sandbox & Test Data
Sandbox is a full copy of the verification pipeline with its own credentials and its own data. Use it to build and prove your integration before any real customer reaches it.
Access
| What you need | How you get it |
|---|---|
| Sandbox base URL | Issued by Inyo at onboarding |
client_id / client_secret | Issued by Inyo at onboarding, separate from production |
webhookSecret | Issued by Inyo at onboarding, separate from production |
| Network access | Your egress IP ranges must be allowlisted β send them to your Inyo contact |
A registered webhookUrl | Optional, but needed to exercise webhook delivery. A tunnel works during development |
Sandbox and production credentials are never interchangeable, and a sandbox verification is never a valid basis for a real onboarding decision.
Two Ways to Get an Outcome
Sandbox performs real document analysis by default β the same extraction, authenticity, liveness, and face-match pipeline as production. Submit images and the result comes from them: a blurry capture genuinely fails, an expired document genuinely declines.
It also accepts a reserved document number that dictates the whole verification outcome, the way a test card number works for payments. Send one in prefill.documentNumber and the session finalizes at creation carrying a full result β no images, no camera, no provider call. See Simulated Verification Outcomes.
Which to reach for:
| Use a reserved number | Use real captures | |
|---|---|---|
| Good for | Proving your code handles each outcome | Proving the pipeline behaves on documents you actually hold |
| Needs | A prefill describing a person | A camera, or images you can post |
| Determinism | Exact β the same value always produces the same rows | Real analysis; a borderline capture can land either way |
| Covers | The eight catalogued outcomes | Everything, including combinations the catalog does not name |
Every result carries a
providerfield:
Value Meaning "inyo"Real analysis produced this result "inyo-simulated"A reserved document number dictated it
"inyo-simulated"never appears in production.
Simulated Verification Outcomes
Send a reserved number in prefill.documentNumber and the session finalizes at creation with a full result. Each value names one scenario: a clean baseline, or that baseline with one check deviating.
capture_exhausted is the exception to both halves of that sentence β see its row below.
curl --request POST \
--url https://{FQDN}/v1/sessions \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"userRef": "user-123",
"prefill": {
"firstName": "Ada", "lastName": "Lovelace", "dateOfBirth": "1990-04-17",
"documentType": "drivers_license", "documentNumber": "99900104",
"issuingCountry": "USA", "issuingState": "PA"
}
}'
The response is already "status": "completed", and document_authentic has failed.
The catalog
Each type reaches all eight scenarios. Send the number with the documentType and jurisdiction shown β a reserved value is matched on that exact pairing.
Send
issuingCountry: "USA". A passport or identity-card value resolves only when the country is named, and nothing tells you when it is missing: the number is treated as an ordinary one, the session stays pending, and it waits for a capture that never comes. A licence also resolves fromissuingStatealone, but send both.
| Scenario | The check it deviates on | drivers_license USA/PA | identity_card USA | passport USA |
|---|---|---|---|---|
clean | β everything passes | 99900101 | 99900201 | 999003001 |
document_expired | document_not_expired | 99900102 | 99900202 | 999003002 |
document_unreadable | document_readable | 99900103 | 99900203 | 999003003 |
document_not_authentic | document_authentic | 99900104 | 99900204 | 999003004 |
portrait_mismatch | face_match | 99900105 | 99900205 | 999003005 |
screen_recapture | document_scene_clear | 99900106 | 99900206 | 999003006 |
sanctions_hit | sanctions_clear | 99900107 | 99900207 | 999003007 |
capture_exhausted | capture_attempts, and the checks the abandoned capture never reached | 99900108 | 99900208 | 999003008 |
capture_exhausted models a holder who ran out of attempts, so it is the one scenario that does not deviate a single check: the capture yielded nothing, every check that needed it is unevaluated, and the session ends failed rather than completed, carrying no decision.
The scenario name is the token the session records and the operator console filters on β one vocabulary for you, your account manager and the analyst reviewing the session.
A deviation is one check, not a decision. What each value pins is which check fails; the decision follows from your tenant's own scoring and decisioning mode, and under non-managed decisioning there is no decision field at all. Read the result rather than assuming the target.
What a simulated session does differently
| Finalized at creation | The response already carries the terminal status. The widget URL is still returned so your integration is unchanged, but the capture link is inert β a capture posted against it is refused |
| Server-to-server | POST /v1/verifications answers with the identifier like any other call, and the simulated result is read at GET /v1/verifications/{verificationId} or pushed to your webhook β so a sandbox run rehearses the whole shape, delivery included. It still requires frontImage β the image is read for the request fingerprint and discarded, never analysed. Two scenarios are refused there: capture_exhausted, because a door holding every capture has no retry budget to exhaust, and portrait_mismatch unless you also post selfieImage |
| Webhook delay | On the session path the webhook is delayed slightly, so it cannot arrive before your own record of the session exists |
Marked by provider | The result reports provider: "inyo-simulated" β in the API response and in the webhook body, which has no headers to carry a marker. X-Inyo-Simulated: true is a response header on the document-number validator only, not on session or verification responses |
Two refusals you will hit
Both are 422 at session creation, and both are deliberate β a dictated result your account could not have produced would teach your code to expect a state it will never see in production.
| Cause | What to do |
|---|---|
prefill.firstName, prefill.lastName or prefill.dateOfBirth missing | A dictated result claims the document was read, and a readable document yields a person. Supply all three |
The scenario deviates on a check your account has not enabled β for example sanctions_hit without sanctions screening | Use a scenario your configuration can produce, or ask your Inyo contact to enable the check |
Sandbox only. These values are armed per environment. If one returns an ordinary verdict and the session waits for a capture, ask your Inyo contact to enable simulation on your sandbox tenant.
Not a fixture for your automated suite. Reach for these while building and proving an integration β by hand, or in a test you run deliberately. For a suite that runs on every commit, capture one of these results once and replay it from your own mock instead of calling us: your tests stay fast, stay green when our sandbox is down, and do not depend on a shared environment you do not control.
The Document-Number Validator
A separate surface from the catalog above: it checks the shape of a declared number and never looks at an image or runs a verification. Reach for it when you need a valid verdict, not an outcome.
curl --request POST \
--url https://{FQDN}/v1/validators/document-number \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"documentType": "drivers_license", "number": "not-a-number", "issuingCountry": "USA", "issuingState": "PA"}'
{
"documentType": "drivers_license",
"issuingCountry": "USA",
"issuingState": "PA",
"valid": false,
"rule": "drivers_license:USA:PA",
"detail": "document number does not match the PA license format"
}
Use it to confirm your handling of all three states β true, false, and null for an unknown jurisdiction. See Document Number Validator.
Reserved Test Document Numbers
These answer the document-number format check only. They are not the values that dictate a session β those are in Simulated Verification Outcomes. Every value listed on this page that is not in that catalog answers the validator alone.
The document-number format check accepts a small set of reserved values that return a chosen verdict on demand. Reach for them when you need a specific valid outcome β most often from remittance sender creation, which runs this check on every declared document β without first learning a jurisdiction's real number format.
Sandbox only. These values are enabled per environment; if the ones below return an ordinary verdict, ask your Inyo contact to enable them on your sandbox tenant.
Every capturable type publishes all three valid states, so you can exercise your own handling per type without learning a jurisdiction's real number format. ssn and itin carry a passing value only β see below.
documentType | issuingCountry | issuingState | number | valid |
|---|---|---|---|---|
drivers_license | USA | PA | 99900001 | true |
drivers_license | USA | PA | 99900002 | false |
drivers_license | USA | PA | 99900003 | null |
drivers_license | BRA | β | 99900000070 | true |
drivers_license | BRA | β | 99900000188 | false |
drivers_license | BRA | β | 99900000296 | null |
passport | USA | β | 999000001 | true |
passport | USA | β | 999000002 | false |
passport | USA | β | 999000003 | null |
identity_card | MEX | β | XXXX000101HXXXXX01 | true |
identity_card | MEX | β | XXXX000101HXXXXX02 | false |
identity_card | MEX | β | XXXX000101HXXXXX03 | null |
ssn | USA | β | 078051120 | true |
itin | USA | β | 912891234 | true |
The identity_card rows are Mexican because there is no US identity-card rule to test against β a US value would be a test case for a check that does not exist. A CURP encodes the holder's name and birth date, so the all-X name field is structurally valid and cannot collide with a real person.
For the same reason, the US identity-card scenario values in the catalog above return valid: null rather than true: a US identity card has no format rule, so document_number_valid is genuinely unevaluated for one β armed or not. Every other scenario value returns the verdict its jurisdiction's real rule gives it.
Send each number with the issuingCountry and issuingState shown. A reserved value is matched on that exact pairing, so the same number under any other jurisdiction is validated by the ordinary rules.
curl --request POST \
--url https://{FQDN}/v1/validators/document-number \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"documentType": "drivers_license", "number": "99900002", "issuingCountry": "USA", "issuingState": "PA"}'
{
"documentType": "drivers_license",
"issuingCountry": "USA",
"issuingState": "PA",
"valid": false,
"rule": "reserved:drivers_license:PA:invalid",
"detail": "reserved sandbox test value β simulated invalid verdict"
}
A simulated result carries the response header X-Inyo-Simulated: true. It is present only when a reserved value produced the result, and absent otherwise β including in production, where it never appears.
Reserved values are real, format-valid numbers for their jurisdiction. That is deliberate: it means an upstream format rule β such as the one remittance sender creation applies before calling this check β passes the value through instead of rejecting it first, which is what lets
99900002reach us and come backfalse.The consequence is that where the reserved values are not enabled, they return the verdict the real format rules give them β
valid: truefor every value except the ITIN, which returnsfalse, and the US identity-card values, which returnnullbecause no US identity-card rule exists. A reserved value can never produce a stricter outcome than an ordinary number would, so leaving one in code is not a safety risk β but it will silently stop simulating, so do not carry one into production.
912891234is the only value that returnsfalsewith simulation off. No voided ITIN specimen exists the way078-05-1120does for SSNs, so it is drawn from a group the IRS reserves for other programmes β the only way to guarantee it can never belong to a real person. The trade is that a strict upstream format rule may reject it before it reaches this check. If that happens on your side, use it against this endpoint directly rather than through a caller that pre-validates.
Testing Result Delivery
Delivery is worth testing on its own, independently of verification outcomes:
- Signature verification β capture one real sandbox webhook body and its
X-Inyo-Signature, then unit-test your verifier against those exact bytes. Add a case that mutates one byte of the body and asserts rejection, and a case that re-serializes the parsed JSON and asserts rejection. Those two cases catch the mistake that breaks most integrations. Then add three that catch the ones you will only hit later: a stalet=outside your tolerance, a header carrying an extrav2=your code does not implement (it must still verify onv1), and a header repeatingv1=twice (it must be rejected outright, not resolved by picking one). - Retry behavior β return
500from your endpoint and confirm you see up to three attempts, then nothing. - Ordering β replay two stored notifications for one session out of order and assert your handler keeps the one with the higher
notifiedAt. - Idempotency β deliver the same result twice and assert your system does not double-process it.
Steps 1, 3, and 4 need no sandbox call at all once you have captured one real payload.
Before You Go Live
| Check | Why |
|---|---|
| Signature verification runs against raw bytes | The most common production failure |
Concurrent results resolve by notifiedAt | Deliveries can arrive out of order |
| A completed outcome can be reversed | Quality-control overrides re-deliver a changed result |
502 retries rather than declining | It is an infrastructure failure, not a customer outcome |
| Production credentials are separate and the base URL is switched | Sandbox tokens are not valid in production |
| No reserved document number reaches production code paths | They stop dictating outside sandbox, so one left behind fails quietly rather than loudly |
Next Steps
- Getting Started β the end-to-end walkthrough
- Receiving Results β signatures, retries, and ordering
