Server-to-Server Verification
When you already have a capture UI — or you are verifying images that were collected earlier — post them directly and get the result back in the response. No session, no widget, no customer link.
Create a Verification
Endpoint: POST /v1/verifications
Authentication: Bearer token with the verifications scope
Content-Type: multipart/form-data
curl --request POST \
--url https://{FQDN}/v1/verifications \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--form user_ref=user-123 \
--form [email protected] \
--form [email protected] \
--form [email protected]
Returns 201 with the same normalized result the widget produces.
Form Fields
| Field | Type | Required | Description |
|---|---|---|---|
user_ref | text | Yes | Your identifier for the person being verified |
front_image | file | Yes | Front of the document — the photo page of a passport, the front of a card |
back_image | file | No | Back of the document. For US licenses and state IDs this carries the barcode |
selfie_image | file | No | Omit to run document checks only — see Document-only verification |
data_check | text | No | true compares extracted data against prefill |
prefill | text | No | A JSON string with the same schema as session prefill |
Note that prefill here is a JSON string inside a multipart field, not a nested object:
--form 'prefill={"first_name":"Ana","last_name":"Silva","date_of_birth":"1988-03-04"}' \
--form data_check=true
Send the Back of the Card
For US driver's licenses and state IDs, the back carries a PDF417 barcode encoding the cardholder data. Inyo decodes it on receipt, and because the barcode is self-verifying — the symbology carries its own error correction — it outranks visual-zone OCR for the fields it contains. Decoding is rotation-independent, so an upside-down back still reads.
A missing, unreadable, or non-barcode back is a silent no-op: OCR values stand and no check changes. There is no downside to sending it, and a measurable accuracy gain when it decodes. Passports carry a machine-readable zone on the photo page instead, so front_image alone is sufficient for them.
Document-only Verification
Omit selfie_image to verify the document without biometrics. The document checks run — readability, format, expiry, authenticity, jurisdiction — and no face checks appear in checks[].
One consequence to plan for: a document-only verification carries no biometric score. If you have a review threshold configured, there is nothing for it to compare against, and an unmeasured verification is treated as unmeasured, not confident — so it routes to in_review rather than auto-approving. Send a selfie, or leave the review threshold unset, if you need document-only verifications decided synchronously.
in_review Is Not a Final Answer
POST /v1/verifications returns the result synchronously, but a 201 response does not guarantee a terminal decision. status can be in_review, meaning an analyst has yet to decide.
This happens when you have configured a review threshold, or when held rejections are enabled and the checks rejected the document. With neither configured, every verification comes back decided.
When it does happen:
| Channel | Behavior |
|---|---|
| Webhook | With a webhook_url configured, the decided result is POSTed to it like any other result — see Result notifications |
| Polling | GET /v1/sessions/{session_id} returns the live status and result, updated in place, with result.manual_review naming who decided and why |
Use session_id from the response body as the identifier for both. No webhook is sent for the original synchronous answer — the 201 already delivered it. See Manual Review for the full picture.
Error Responses
| Status | Cause | How to handle |
|---|---|---|
401 / 403 | Missing or invalid token, or a token without the verifications scope | See Authentication |
422 | prefill is not valid JSON or violates the prefill schema (including document-number format) | Fix the payload — the detail names the problem |
422 | data_check=true with no prefill | Provide a prefill payload to compare against |
502 | The verification service was unavailable | Transient — retry. This is an infrastructure failure, not a decline. Do not treat it as a negative outcome for the customer |
Widget or Server-to-Server?
Both entry points share the same pipeline and the same result shape, and your thresholds, review routing, accepted jurisdictions, and enrichment options apply identically. Two settings are session-level by nature and have no effect here: the allowed document types and prefill.document_type locking both constrain what the widget offers a customer, so on this endpoint the document type is simply whatever the images turn out to be.
The differences that matter:
| Hosted widget | Server-to-server | |
|---|---|---|
| Capture quality | Guided, with live framing and glare feedback | Yours to control |
| Failed capture | Customer is coached and retries | Returns a result; retrying is your decision |
| Result delivery | Webhook or signed redirect | The response body, plus later changes by webhook |
| Presentation-attack defense | Same checks | Same checks, but with no live capture context to draw on |
| Compliance evidence | Inyo retains the guided capture and optional selfie video | Only the images you send |
The retry difference is about coaching a live customer — it does not change what your configuration means or how the decision is reached.
Next Steps
- Checks & Decisions — the result payload in full
- Manual Review — handling
in_review - Receiving Results — webhook notifications for later changes
