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.
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.