Description
KYC Screening API
The KYC Screening API provides universal Know Your Customer screening capabilities with support for multiple providers. It implements a database-first strategy for efficient screening and comprehensive search capabilities.
Purpose and use
KYC screening records document identity checks, risk signals, and review status for individuals and other screened parties. The module supports onboarding decisions, periodic review, remediation, and evidence retention for compliance audits.
Who uses this. Compliance analysts, onboarding teams, and operations managers use KYC screenings when deciding whether a person can be onboarded, needs enhanced due diligence, or requires remediation.
How it works. A screening records the subject, screening result, status, timestamps, and supporting metadata. Reviewers use the stored screening history to compare current status against previous checks and to explain decisions during audit review.
What users do. Users start a screening, review the result, search historical screenings, check statistics, and update the review state once a compliance decision has been made.
Outcomes and side effects. Screening outcomes inform customer activation, task creation, risk classification, and audit trails. They do not post ledger entries, but they can block or delay onboarding and product access.
Related manuals: KYB, Risk Assessment, Customers, Tasks.
Core API Methods
The KYC API provides three core methods that work universally across all providers:
1. Screen Entity (POST)
POST /v1/kyc/screening
Universal screening method with db-first check strategy. Checks database for existing results first, then performs new screening if needed.
Query Parameters:
force(string): Force new screening, bypassing database cache (case-insensitive, accepts 'true', 'TRUE', 'True', etc., default: false)
Request Body:
{
"entity_type": "individual",
"individual": {
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1980-01-01",
"nationality": "US",
"address": {
"country": "US",
"city": "New York"
}
},
"screening_types": ["sanctions", "peps"],
"metadata": {
"source": "customer_onboarding",
"case_id": "123e4567-e89b-12d3-a456-426614174000"
}
}Examples with Force (case-insensitive):
POST /v1/kyc/screening?force=true
POST /v1/kyc/screening?force=TRUE
POST /v1/kyc/screening?force=TrueResponse:
{
"request_id": "123e4567-e89b-12d3-a456-426614174000",
"entity_type": "individual",
"overall_risk_level": "medium",
"overall_score": 0.75,
"screening_results": [
{
"screening_type": "sanctions",
"risk_level": "low",
"score": 0.0,
"status": "completed",
"summary": "No matches found",
"last_updated": "2025-09-03T18:32:38.854422+02:00"
},
{
"screening_type": "peps",
"risk_level": "medium",
"score": 0.75,
"status": "completed",
"summary": "1 potential match found",
"last_updated": "2025-09-03T18:32:38.854422+02:00"
}
],
"total_matches": 1,
"has_matches": true,
"requires_review": true,
"screened_at": "2025-09-03T18:32:38.396754Z",
"recommendations": [
"Review PEP match for potential risk",
"Conduct additional due diligence"
],
"metadata": {
"provider": "opensanctions",
"user_id": "66260c8c-7743-4dfb-84ba-ae9cf6794c9a"
}
}2. Get All Screenings (GET)
GET /v1/kyc/screening
Comprehensive search method for existing screening records in the database with advanced query capabilities.
Advanced Query Parameters:
search.*- Advanced search operators (see Advanced Search section below)limit(integer): Number of results to return (default: 10, max: 100)offset(integer): Number of results to skip (default: 0)sort(string): Sort fields (comma-separated, prefix with - for descending)stack(string): Group results by field
Example:
GET /v1/kyc/screening?search.entity_type=person&search.has_matches=true&limit=203. Get Screening by ID (GET)
GET /v1/kyc/screening/{id}
Retrieve an existing screening record from the database by ID.
Query Parameters:
include_matches(string): Include detailed match results (case-insensitive, accepts 'true', 'TRUE', 'True', etc., default: false)include_response(string): Include full KYC provider API response (case-insensitive, accepts 'true', 'TRUE', 'True', etc., default: false)
Examples:
GET /v1/kyc/screening/123e4567-e89b-12d3-a456-426614174000?include_matches=true
GET /v1/kyc/screening/123e4567-e89b-12d3-a456-426614174000?include_matches=TRUE
GET /v1/kyc/screening/123e4567-e89b-12d3-a456-426614174000?include_matches=True&include_response=trueStatistics
Get Screening Statistics
GET /v1/kyc/screening/stats
Get comprehensive statistics about screening operations.
Response:
{
"total_screenings": 1250,
"total_matches": 45,
"match_rate": 0.036,
"by_entity_type": {
"person": 800,
"business": 400,
"legal": 50
},
"by_collection": {
"default": 600,
"sanctions": 400,
"peps": 250
},
"by_status": {
"completed": 1200,
"pending": 30,
"failed": 20
},
"recent_screenings": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"entity_type": "person",
"entity_name": "John Doe",
"status": "completed",
"screening_date": "2025-01-02T10:30:00Z",
"has_matches": true
}
]
}Advanced Search Capabilities
The GET /v1/kyc/screening endpoint supports advanced search operations using the query package:
Search Operators
All search parameters must be prefixed with search.:
| Operator | Description | Example |
|---|---|---|
| eq (default) | Equal | search.entity_type=person |
| ne | Not Equal | search.status.ne=failed |
| gt | Greater Than | search.match_count.gt=0 |
| gte | Greater Than or Equal | search.highest_score.gte=0.8 |
| lt | Less Than | search.screening_date.lt=2024-12-31T23:59:59Z |
| lte | Less Than or Equal | search.highest_score.lte=0.5 |
| in | In Array | search.status.in=completed,pending |
| nin | Not In Array | search.entity_type.nin=vessel,aircraft |
| like | Pattern Match | search.entity_name.like=John |
Searchable Fields
id,entity_type,entity_name,entity_schema,collectionstatus,screening_date,match_count,highest_score,has_matchesrequested_by,assigned_to,assigned_at,case_idcreated_at,updated_at
Pagination & Sorting
limit(integer): Results per page (default: 10, max: 100)offset(integer): Results to skip (default: 0)sort(string): Sort fields (comma-separated, prefix with-for descending)stack(string): Group results by field
Examples
Basic Search:
GET /v1/kyc/screening?search.entity_type=person&search.has_matches=trueAdvanced Search:
GET /v1/kyc/screening?search.match_count.gt=0&search.highest_score.gte=0.8&sort=-screening_date&limit=20Grouped Results:
GET /v1/kyc/screening?search.has_matches=true&stack=entity_typeUniversal API Design
The KYC API is designed with a universal interface that works across all providers. This eliminates the need for provider-specific endpoints and ensures consistency regardless of the underlying KYC provider being used.
Key Benefits:
- Provider Agnostic: Same API works with OpenSanctions, ComplyAdvantage, LexisNexis, etc.
- Database-First Strategy: Checks existing data before making external calls
- Consistent Interface: No need to learn different endpoints for different providers
- Future Proof: Easy to add new providers without changing the API
- Simplified Integration: Three core endpoints handle all KYC operations
Entity Types
The API supports the following entity types:
- individual: Individual persons
- company: Business entities
- other: Other entity types
Screening Types
The universal interface supports the following screening types:
- sanctions: Sanctions and watchlists screening
- peps: Politically Exposed Persons screening
- adverse_media: Adverse media and negative news screening
- identity_verification: Identity verification and validation
- comprehensive: Comprehensive screening across all types
Data Storage
All screening results are stored in the kyc.screening table with intelligent caching:
- Database-First Strategy: Checks existing results before external calls
- Force Option: Use
force=trueto bypass cache and force new screening - Comprehensive Search: Advanced query capabilities for existing records
- Provider Agnostic: Works with any KYC provider through universal interface
Identity Verification (IDV)
Beyond list screening, the KYC module exposes signatory-focused identity verification backed by Ondato. IDV follows the same transport conventions as screening and is fully wired through , , and .
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /v1/kyc/{customer_id}/signatories/{signatory_id}/idv | Creates a provider session for the referenced signatory. No body is required—the service pulls persona + user metadata inside the transaction. |
GET | /v1/kyc/{customer_id}/signatories/{signatory_id}/idv?refresh=true | Returns cached status, optionally forcing a refresh when refresh=true (string match). The handler enforces UUID ownership before delegating to the service. |
Both endpoints require the standard auth middleware (auth.WrapWithMiddlewares in ). The POST response mirrors models.IDVInitiationResponse, while the GET response returns models.IDVStatusResponse with verification_id, provider session ID, URLs, status, timestamps, and the latest provider payload stored under response_data.
Status Refresh Strategy
GetSignatoryIDV implements a hybrid cache in :
- Calling
GET .../idvwithoutrefreshserves cached data unless the session is active (pendingorin_progress) and older thankyc.idv.settings.cache_refresh_minutes. - Supplying
refresh=truebypasses the age check and fetches from Ondato even for terminal statuses. - Completed/failed/expired/aborted sessions never auto-refresh, but you can still force-refresh them.
- If the provider call fails (
refreshFromProvider), the service logs a warning and returns the cached snapshot so the endpoint never errors just because Ondato is down.
| Session status | Cache age | refresh query | Behavior |
|---|---|---|---|
pending / in_progress | TTL | false | Automatically calls Ondato and persists the update |
pending / in_progress | any | true | Forces provider refresh |
completed / failed / expired / aborted | any | false | Always returns cached data |
completed / failed / expired / aborted | any | true | Forces provider refresh |
updated_at in the response reflects the last database write (initiation, webhook, auto-refresh, or poller). completed_at mirrors provider data and is set by webhooks or by applyProviderUpdate whenever Ondato reports a terminal state.
Configuration
All IDV knobs live under kyc.idv.* and ship with documented defaults (). The sample configuration in .samples/config/kyc.yaml looks like:
idv:
provider: ondato
ondato:
api_url: https://idvapi.ondato.com
token_url: https://id.ondato.com/connect/token
client_id: your_client_id
client_secret: your_client_secret
verification_url: https://idv.ondato.com
webhook_secret: your_webhook_secret
settings:
cache_refresh_minutes: 5
poller:
enabled: false
interval_seconds: 45
batch_size: 50
worker_concurrency: 4
stale_minutes: 5
urls:
callback: https://your-domain.com/webhooks/ondato
success: https://your-domain.com/onboarding/success
failure: https://your-domain.com/onboarding/retry
queue:
driver: rabbitmq
host: localhost
routing_key: ondato.*
name: ondato.webhooks
listener_route: /webhooks/ondatoKey settings and how the code uses them:
- Cache refresh TTL (
kyc.idv.settings.cache_refresh_minutes): Loaded inNewIDVService. Values ≤0 fall back toconstants.KYCIDVDefaultCacheMinutes(5). Active sessions exceeding this age auto-refresh inshouldAutoRefresh.
Set provider: mock plus dummy URLs when you need deterministic integration tests; the service will short-circuit with errs.MsgIDVNotSupported if the driver is missing.
Operational Notes
- Webhooks are the primary source of truth; the queue consumer rejects malformed payloads (
ShouldRetryreturnsfalsefor permanent failures) and applies updates viaapplyProviderUpdate. - The poller and
refresh=trueendpoint are safety nets when a webhook is delayed or missed. GetSignatoryIDValways validates{customer_id}←→{signatory_id}ownership before returning data, ensuring tenants cannot see each other’s verifications.