Inyo

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 needHow you get it
Sandbox base URLIssued by Inyo at onboarding
client_id / client_secretIssued by Inyo at onboarding, separate from production
webhookSecretIssued by Inyo at onboarding, separate from production
Network accessYour egress IP ranges must be allowlisted β€” send them to your Inyo contact
A registered webhookUrlOptional, 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 numberUse real captures
Good forProving your code handles each outcomeProving the pipeline behaves on documents you actually hold
NeedsA prefill describing a personA camera, or images you can post
DeterminismExact β€” the same value always produces the same rowsReal analysis; a borderline capture can land either way
CoversThe eight catalogued outcomesEverything, including combinations the catalog does not name

Every result carries a provider field:

ValueMeaning
"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 from issuingState alone, but send both.

ScenarioThe check it deviates ondrivers_license USA/PAidentity_card USApassport USA
cleanβ€” everything passes9990010199900201999003001
document_expireddocument_not_expired9990010299900202999003002
document_unreadabledocument_readable9990010399900203999003003
document_not_authenticdocument_authentic9990010499900204999003004
portrait_mismatchface_match9990010599900205999003005
screen_recapturedocument_scene_clear9990010699900206999003006
sanctions_hitsanctions_clear9990010799900207999003007
capture_exhaustedcapture_attempts, and the checks the abandoned capture never reached9990010899900208999003008

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 creationThe 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-serverPOST /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 delayOn the session path the webhook is delayed slightly, so it cannot arrive before your own record of the session exists
Marked by providerThe 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.

CauseWhat to do
prefill.firstName, prefill.lastName or prefill.dateOfBirth missingA 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 screeningUse 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.

documentTypeissuingCountryissuingStatenumbervalid
drivers_licenseUSAPA99900001true
drivers_licenseUSAPA99900002false
drivers_licenseUSAPA99900003null
drivers_licenseBRAβ€”99900000070true
drivers_licenseBRAβ€”99900000188false
drivers_licenseBRAβ€”99900000296null
passportUSAβ€”999000001true
passportUSAβ€”999000002false
passportUSAβ€”999000003null
identity_cardMEXβ€”XXXX000101HXXXXX01true
identity_cardMEXβ€”XXXX000101HXXXXX02false
identity_cardMEXβ€”XXXX000101HXXXXX03null
ssnUSAβ€”078051120true
itinUSAβ€”912891234true

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 99900002 reach us and come back false.

The consequence is that where the reserved values are not enabled, they return the verdict the real format rules give them β€” valid: true for every value except the ITIN, which returns false, and the US identity-card values, which return null because 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.

912891234 is the only value that returns false with simulation off. No voided ITIN specimen exists the way 078-05-1120 does 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:

  1. 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 stale t= outside your tolerance, a header carrying an extra v2= your code does not implement (it must still verify on v1), and a header repeating v1= twice (it must be rejected outright, not resolved by picking one).
  2. Retry behavior β€” return 500 from your endpoint and confirm you see up to three attempts, then nothing.
  3. Ordering β€” replay two stored notifications for one session out of order and assert your handler keeps the one with the higher notifiedAt.
  4. 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

CheckWhy
Signature verification runs against raw bytesThe most common production failure
Concurrent results resolve by notifiedAtDeliveries can arrive out of order
A completed outcome can be reversedQuality-control overrides re-deliver a changed result
502 retries rather than decliningIt is an infrastructure failure, not a customer outcome
Production credentials are separate and the base URL is switchedSandbox tokens are not valid in production
No reserved document number reaches production code pathsThey stop dictating outside sandbox, so one left behind fails quietly rather than loudly

Next Steps