Start identity verification
Opens a provider verification session for one signatory and returns the URL to send them to. Answers 201 Created. Takes NO request body. Only one session may be open per signatory: a second call while one is in progress is 409. THE RESPONSE SET BELOW IS NOT CLOSED. The Ondato driver forwards the provider's own HTTP status verbatim from three sites, TWO of which this route reaches: CreateIDVSession, and requestAccessToken — its OAuth token endpoint, called through getAccessToken on this same path and identifiable by reason=token_endpoint_error. The third, GetIDVSession, is reached only from refreshFromProvider, i.e. from the IDV GET (where the error is swallowed) and from the background poller, so its verbatim status reaches no client at all. Whatever Ondato answers >= 400 becomes the status here, so a caller can receive a status this operation does not declare at all: an upstream 422 is rendered as 422, and any status apireply's switch does not know — 402 and 405 aside, it covers 200, 400, 401, 402, 403, 404, 405, 408, 409, 422, 429, 502, 503 and 504 — falls to 500. Treat kyc_m.idv_provider_error as the signal and the status as advisory.
Authorization
bearerAuth JWT token from the authentication endpoint.
In: header
Path Parameters
Customer owning the signatory. A non-UUID is 400 common.invalid_input with field=customer_id.
Signatory to verify. A non-UUID is 400 common.invalid_input with field=signatory_id.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/kyc/497f6eca-6276-4993-bfeb-53cbbbba6f08/signatories/497f6eca-6276-4993-bfeb-53cbbbba6f08/idv"{
"verification_id": "5bcbf7ac-c998-46ce-aa6e-a0c1a3f1f5bd",
"session_id": "string",
"verification_url": "string",
"expires_at": "2019-08-24T14:15:22Z"
}{
"status": 400,
"message": "Invalid input",
"code": "common.invalid_input",
"class": "validation"
}{
"status": 401,
"message": "Unauthorized",
"code": "common.unauthorized",
"class": "business"
}{
"status": 403,
"message": "No access to the record",
"code": "common.rbac_no_rec_access",
"class": "business"
}{
"status": 404,
"message": "Signatory record not found",
"code": "kyc_m.idv_signatory_not_found",
"class": "business"
}{
"status": 409,
"message": "Identity verification already in progress",
"code": "kyc_m.idv_already_in_progress",
"class": "business"
}{
"status": 429,
"message": "Rate limit for 203.0.113.7 to POST:/v1/kyc/screening exceeded.",
"code": "rate_limits_m.exceeded",
"class": "temporary",
"retryable": true
}{
"status": 500,
"message": "Identity verification provider not supported",
"code": "kyc_m.idv_not_supported",
"class": "business"
}{
"status": 502,
"message": "Ondato provider returned an error",
"code": "kyc_m.idv_provider_error",
"class": "temporary",
"retryable": true
}{
"overall_status": "unhealthy",
"message": "Service is shutting down",
"timestamp": "2026-08-27T15:04:05Z"
}Returns the cached verification state for one signatory. ?refresh=true re-reads it from the provider first. status can be aborted, which this spec previously omitted — a terminal outcome distinct from failed and expired. A PROVIDER FAULT IS NOT AN ERROR ON THIS ROUTE. No 502 is declared because none is reachable: tryRefreshVerification swallows every error from refreshFromProvider, logs a warning and returns the cached verification. ?refresh=true against a dead provider answers 200 with STALE data and no indication that the refresh failed — compare updated_at against now if freshness matters — tryRefreshVerification returns the row untouched on a provider failure, so updated_at is the only staleness signal in the body. DO NOT CALL THIS FOR A SIGNATORY WITH NO VERIFICATION. That case is an unguarded nil dereference, not a 404 — see the 404 below.
Returns one stored screening, rendered as UniversalKYCResponse rather than as the stored row. The two query parameters below are accepted and parsed but DISCARDED before they can change the body: matches are always included, and the provider's raw response is never included, whatever is sent.