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.


Sandbox Runs Real Verification

Sandbox performs real document analysis β€” the same extraction, authenticity, liveness, and face-match pipeline as production. No test document number produces a canned verification outcome the way test card numbers work for payments: approval, decline, and review all come from the images you submit.

The one exception is the document-number format check, which does accept reserved values β€” see Reserved Test Document Numbers. It checks the shape of a declared number and never looks at an image, so it is a separate surface from the verification pipeline described here.

The practical consequence: test with real documents you personally control, or with documents issued for testing. A verification will genuinely fail if the image is blurry, the document is expired, or the selfie does not match β€” which is exactly what makes sandbox useful for proving your error handling.

Every result carries a provider field. "inyo" means real analysis produced it. "inyo-mock" means a simulator did β€” which never happens in production, and tells you unambiguously that a result did not come from real verification.


Driving Each Outcome

TargetHow to produce it
approvedA valid, unexpired document you control, well lit and in frame, with a selfie of the same person
declined β€” expired documentAn expired document. document_not_expired fails
declined β€” face mismatchA document belonging to one person with a selfie of another. face_match fails
declined β€” screen recapturePhotograph the document off a phone or monitor screen. document_scene_clear fails
declined β€” unreadableA deliberately blurry or partially covered capture. document_readable fails
declined β€” capture attemptsFail capture repeatedly in the widget until the limit is reached, adding a capture_attempts check
in_review β€” soft checkSend dataCheck: true with a prefill name or date of birth that does not match the document. document_data_match fails
in_review β€” held rejectionAsk Inyo to enable held rejections on your sandbox tenant, then produce any readable-document decline
in_review β€” borderline passAsk Inyo to set a review threshold on your sandbox tenant above your face-match threshold, then verify with a marginal selfie
expiredCreate a session and leave it unused past the link's 48-hour lifetime
422 responsesRequest a document type you do not have enabled, or send a malformed prefill.documentNumber
502Not reproducible on demand β€” handle it as a transient retry path in code

Ask for a separate sandbox tenant if you need conflicting configurations. Review routing and held rejections are tenant-level settings, so exercising both a normal decline and a held decline means changing configuration between runs β€” or having two sandbox tenants.


Testing Without a Camera

The document-number format validator needs no images and consumes no verification, which makes it the fastest thing to integrate against first:

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": "dl: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

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.

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. 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
in_review has a pending state in your modelIt is neither approved nor rejected, and it arrives on redirects too
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
A reconciliation job polls non-terminal sessionsDelivery is best-effort; GET /v1/sessions/{sessionId} is authoritative

Next Steps