Verifying Documents
To advance a sender to higher compliance levels (Level 2 and above), their identity documents must be verified. There are two ways to get documents verified:
- KYC Verification Session (recommended) β create a session and hand your user a hosted widget URL. The widget captures the document photos and a selfie, verifies them, and the result flows back to the sender's record automatically.
- Direct document upload (deprecated) β POST document image files yourself and wait for OCR/manual review.
β οΈ The direct upload endpoints are deprecated. New integrations should use KYC Verification Sessions. Existing upload integrations keep working, but the session flow adds selfie liveness, document-portrait face matching, and presentation-attack defense that plain file uploads cannot provide.
KYC Verification Sessions (Recommended)
Endpoint: POST /organizations/{tenant}/v2/people/{personId}/kycSession
Authentication: Tenant-level (x-api-key)
Creates an Inyo360 Identity Verification session for the person. You supply only widget-presentation parameters β the document to verify is resolved automatically from the person's declared documents, and the session is prefilled from the person record (name, date of birth, document number, issuing state, nationality), so the user only has to capture their document and selfie.
Declare the document first. The person must have a non-expired, KYC-verifiable document on file (added via
POST /v2/peopledocuments[]) before you can open a session β otherwise the request returns422 NO_VERIFIABLE_DOCUMENT.
Request Body
Both fields are required:
| Field | Type | Required | Description |
|---|---|---|---|
language | string | Yes | Widget language, xx or xx-XX (e.g. en, es, pt-BR) |
redirectUrl | string (URL) | Yes | Where to send the user after they finish the widget flow |
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/v2/people/$PERSON_ID/kycSession \
--header 'Content-Type: application/json' \
--header "x-api-key: $API_KEY" \
--data '{
"language": "en",
"redirectUrl": "https://your-app.example/kyc/done"
}'
How the Document Is Selected
Inyo walks the person's declared documents in priority order and verifies the first eligible one:
drivers_licensepassport- The identity-card family, most-specific first:
dni,cc,cpf,consular_id,voter_id,id
Documents with an expiration date in the past are skipped (a person with an expired driver's license and a valid passport gets the passport); documents without an expiration date on file are trusted. ssn, itin, and other are never selected β they don't map to the KYC document vocabulary.
Data cross-checking is always on: the KYC layer compares the data extracted from the captured document against the prefilled person record.
Response (201):
{
"sessionId": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
"status": "pending",
"widgetUrl": "https://{FQDN}/verify/8Kd2mQ..."
}
The Flow
- Create the session and open
widgetUrlfor your user (link, redirect, or webview β see Widget Delivery). - The user photographs their document and takes a selfie in the widget.
- Inyo receives the signed verification result directly β you don't handle any document images.
- On a verified result, the person's document records are created/updated automatically with the verification verdict, and the
DocumentUpdatedEventswebhook fires. - Check the sender's new level via
GET /participants/{id}/complianceLevels.
Sessions move through these states on the Inyo side: PENDING β VERIFIED, REJECTED, PENDING_REVIEW (manual review at the KYC layer), or EXPIRED.
Errors
| HTTP | Error | Cause |
|---|---|---|
404 | NOT_FOUND | Person not found in your tenant |
422 | NO_VERIFIABLE_DOCUMENT | The person has no non-expired, KYC-verifiable document on file β declare one via POST /v2/people (documents[]) first |
422 | VALIDATION_ERROR | language or redirectUrl missing/malformed |
502 | KYC_UPSTREAM_ERROR | The KYC service is temporarily unavailable β retry the request |
Legacy: Direct Document Uploads (Deprecated)
β οΈ Deprecated. Use KYC Verification Sessions instead. These endpoints remain available for existing integrations. Uploaded documents are verified by AI OCR (where enabled for your tenant) or by the Inyo compliance team.
Identity Documents
Endpoint: POST /organizations/{tenant}/people/{personId}/documents/documentId/{subtype}/upload
Authentication: Tenant-level (x-api-key)
Content-Type: multipart/form-data
The {subtype} path segment is the document type. Both wire format (passport, driversLicense, ...) and canonical uppercase (PASSPORT, DRIVER_LICENSE, ...) are accepted. The full set:
| Wire value | Canonical value(s) | Description |
|---|---|---|
passport | PASSPORT | Passport |
driversLicense | DRIVER_LICENSE | Driver's license |
nationalId | DNI, CC, ID | National ID β DNI (Spain/Argentina/Peru), CC (Colombia cΓ©dula), or generic government ID |
cpf | CPF | CPF (Brazil) |
consularId | CONSULAR_ID | Consular ID (matrΓcula consular) |
voterId | VOTER_ID | Voter ID |
ssn | SSN | US Social Security card |
itin | ITIN | US ITIN (IRS) |
other | OTHER | Other government-issued document |
The three national-ID variants (
DNI,CC,ID) collapse to the single wire valuenationalId, so clients don't have to handle country-specific naming.
An unknown subtype returns 422 INVALID_SUBTYPE with the allowed list in the response. PROOF_OF_FUNDS is deliberately not accepted here β use the dedicated source of funds endpoint below.
Form Fields
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The document image β pdf, jpg, jpeg, or png, max 10 MB |
idNumber | string | No | The document number printed on the document |
issuer | string | No | Issuing authority or state |
expirationDate | string | No | Format YYYY-MM-DD |
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/people/$PERSON_ID/documents/documentId/PASSPORT/upload \
--header "x-api-key: $API_KEY" \
--form "file=@/path/to/passport.jpg" \
--form "idNumber=AB1234567" \
--form "expirationDate=2030-12-31"
Response (201):
{
"id": "4ec66735-216b-4ab4-b1d7-00558baa6d85",
"subtype": "PASSPORT",
"fileName": "passport.jpg",
"verificationStatus": "PENDING",
"createdAt": "2026-08-04T12:00:00+00:00",
"idNumber": "AB1234567",
"issuer": null,
"expirationDate": "2030-12-31"
}
Source of Funds
Proof of the sender's source of funds (bank statement, pay stub), required for higher compliance tiers.
Endpoint: POST /organizations/{tenant}/people/{personId}/documents/sourceOfFunds/upload
Authentication: Tenant-level (x-api-key)
Content-Type: multipart/form-data
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/people/$PERSON_ID/documents/sourceOfFunds/upload \
--header "x-api-key: $API_KEY" \
--form "file=@/path/to/bank-statement.pdf"
Same file rules as identity documents (pdf/jpg/jpeg/png, max 10 MB). The upload is stored under the PROOF_OF_FUNDS type.
Document Verification Status
After uploading, documents are verified asynchronously β by AI OCR (if enabled for your tenant) within minutes, otherwise by the compliance team.
Get Current Verification Status
Endpoint: GET /organizations/{tenant}/documents/{documentId}/verificationStatus/current
Authentication: Tenant-level
curl --request GET \
--url https://{FQDN}/organizations/$TENANT/documents/$DOCUMENT_ID/verificationStatus/current \
--header "x-api-key: $API_KEY"
Get Verification Status History
Endpoint: GET /organizations/{tenant}/documents/{documentId}/verificationStatus
Authentication: Tenant-level
curl --request GET \
--url https://{FQDN}/organizations/$TENANT/documents/$DOCUMENT_ID/verificationStatus \
--header "x-api-key: $API_KEY"
Returns entries with status, reason, verifiedBy, and createdAt β the reason field explains rejections so you can guide the user to re-upload.
List a Person's Documents
Endpoint: GET /organizations/{tenant}/people/{personId}/documents
Authentication: Tenant-level
curl --request GET \
--url https://{FQDN}/organizations/$TENANT/people/$PERSON_ID/documents \
--header "x-api-key: $API_KEY"
Verification Statuses
| Status | Description |
|---|---|
PENDING | Document uploaded, awaiting verification |
VERIFIED | Document accepted β compliance level may upgrade |
REJECTED | Document rejected β check the reason and re-upload |
Webhook Notifications
Register for the DocumentUpdatedEvents webhook to be notified when a document's verification status changes β one event on upload (PENDING) and another when verification completes (VERIFIED/REJECTED). See Webhooks for the payload reference.
Best Practices
- Upload high-quality images β blurry or cropped images will be rejected.
- Pass
idNumberandexpirationDatewhen you have them β they enrich the compliance record and speed up review. - Check compliance level after verification β once a document is
VERIFIED, callGET /participants/{id}/complianceLevelsto see if the sender has been upgraded. - Handle rejections gracefully β surface the rejection
reasonand prompt the user to re-upload. - Use webhooks instead of polling β register for
DocumentUpdatedEventsto be notified immediately.
Test Data
For sandbox testing, see Test Data and Sandbox Testing β certain name/address combinations trigger specific compliance behaviors (approval, hold, rejection).
