Screening API
Perform real-time screening of individuals and organizations against global sanctions, watchlists, politically exposed persons (PEPs), and adverse media sources.
Real screening checks are only performed in live mode. Test mode returns mock results for development purposes.
What Gets Screened
- Sanctions: Lists including OFAC, UN, EU, UK HMT, DFAT
- Watchlists: Regulatory enforcement, disqualified directors
- PEPs: Politically Exposed Persons and their relatives (RCAs)
- Adverse Media: Negative news, fraud allegations, financial crime
How Screening Works
Screening is an asynchronous process that follows this workflow:
POST /screening/
asynchronously
Webhook or GET
Each entity can only be screened once. Subsequent requests return 409 Conflict with existing screening details.
Screen Entity
Submit an existing entity for KYC/AML screening. Screening runs asynchronously (usually a few seconds) with results delivered via webhook or by polling the results endpoint.
/api/v1/screening/
Include an Idempotency-Key header (16-255 characters) to ensure screening requests are processed exactly once. Use a unique key per entity to prevent duplicate screenings.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
entity_id |
uuid | Yes | ID of existing entity to screen. Entity must belong to your tenant. |
webhook_url |
string | No | Optional webhook URL for completion notification. Use tenant webhooks if not provided. |
Success Response (202 Accepted)
{
"screening_id": "62ee34a4-28f0-4a83-a016-7441e7900d7e",
"entity_id": "716ce8e4-8c1f-4093-b4c7-9e897c2bb7cc",
"name": "Test Company Ltd",
"entity_type": "company",
"screening_status": "pending",
"message": "Test screening initiated (no credits charged, mock data will be returned)",
"test_mode": true
}
Error Response - Already Screened (409 Conflict)
{
"error": "Entity already screened",
"detail": "Entity has existing screening with status: completed",
"screening_id": "62ee34a4-28f0-4a83-a016-7441e7900d7e",
"entity_id": "550e8400-e29b-41d4-a716-446655440000",
"screened_at": "2026-01-15T10:30:00Z"
}
Get Screening Results
Retrieve screening results for an entity. Returns all screenings performed on the entity along with risk profiles (matches) detected by ComplyAdvantage.
/api/v1/screening/{entity_id}/
Response Structure
{
"screening": {
"id": "f00354f7-7762-4903-9200-4cf3342d1e59",
"entity": {
"id": "0f0e0942-83c9-4504-9b1e-aac9ac341602",
"name": "John Doe",
"entity_type": "individual"
},
"status": "completed",
"risk_score": 3.5,
"screening_type": "initial",
"is_test_data": false,
"creator_profile": {
"id": "5c4fdd14-93c8-4653-9c2b-a45f58654599",
"name": "Jane Smith",
"email": "[email protected]"
},
"decider_profile": {
"id": "5c4fdd14-93c8-4653-9c2b-a45f58654599",
"name": "Jane Smith",
"email": "[email protected]"
},
"created_at": "2026-01-07T14:35:36.258555+00:00",
"updated_at": "2026-01-09T14:04:11.489236+00:00",
"cases": [
{
"id": "c8f16c1d-8e55-4ade-afe4-dbc472f49a31",
"title": "Case for John Doe - Onboarding",
"status": "closed",
"decision": "acceptable_risk",
"assigned_to": null,
"closed_by": {
"id": "5c4fdd14-93c8-4653-9c2b-a45f58654599",
"name": "Jane Smith",
"email": "[email protected]"
},
"closed_at": "2026-01-09T14:04:11.448360+00:00",
"created_at": "2026-01-07T14:35:39.224120+00:00",
"updated_at": "2026-01-09T14:04:11.448618+00:00",
"alerts": [
{
"id": "5bc52924-8a4f-48ae-acaa-13027deae060",
"risk_profiles": [
{
"id": "f67f165d-0d1e-4eb7-81e6-3494a260dcd4",
"name": "John Doe - Adverse Media, PEP",
"entity_type": "individual",
"status": "false_positive",
"matching_score": 0.3,
"matching_name": "John Doe",
"match_type": [
"aka_exact",
"name_variations_removal"
],
"dates_of_birth": [
"1975",
"1975-06-21"
],
"also_known_as": [
"Johnathan Doe",
"John Doe",
"John Doe Jr.",
"John R. Doe"
],
"all_related_countries": [
"AU",
"BR",
"IN",
"IT",
"MX",
"NG",
"US"
],
"has_pep": true,
"has_sanctions": false,
"has_adverse_media": true,
"has_watchlists": false
}
]
}
]
}
]
}
}
Risk Profile Fields (Summary View)
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Unique risk profile identifier |
name |
string | Name of matched entity with risk types (e.g., "John Doe - PEP, Adverse Media") |
entity_type |
string | Type of entity: individual or company |
status |
string | Review status: not_reviewed, in_review, false_positive, true_positive |
matching_score |
float | Confidence score (0.0-100.0). Higher = stronger match. >80 = high confidence, <50 = likely false positive |
matching_name |
string | Exact name from watchlist/database that triggered the match |
match_type |
array | How match was made: name_exact, aka_exact, name_variations_removal, etc. |
dates_of_birth |
array | Known dates of birth for the matched entity (YYYY or YYYY-MM-DD format) |
also_known_as |
array | Aliases and alternative names for the matched entity |
all_related_countries |
array | Country codes (ISO 2-letter) associated with the risk |
has_pep |
boolean | True if PEP data exists (politically exposed person) |
has_sanctions |
boolean | True if sanctions data exists |
has_watchlists |
boolean | True if watchlist data exists |
has_adverse_media |
boolean | True if adverse media articles exist |
Use GET /screening/{entity_id}/detailed/ to get full PEP, sanctions, media, and watchlist JSON data.
Or use GET /risk-profiles/{id}/ to get individual risk profile with all sensitive data.
Understanding Results
Screening results are organized in a hierarchy. Understanding this structure is essential for processing results correctly.
Result Hierarchy
Screening (1 per entity) ├─ screening_id: UUID ├─ status: "completed" | "pending" | "failed" ├─ risk_score: 0-10 (initial risk assessment) └─ Cases (0+ per screening) ├─ case_id: UUID ├─ title: "Case for [Entity] - Onboarding" ├─ status: "open" | "in_review" | "closed" └─ Alerts (1+ per case) ├─ alert_id: UUID └─ RiskProfiles (1+ per alert - the actual matches) ├─ id: UUID ├─ name: "John Smith - PEP, Sanctioned" ├─ matching_score: 0-100 (confidence) ├─ matching_name: "SMITH, John Alexander" ├─ aml_types: ["sanction", "pep"] ├─ countries: ["US", "GB"] ├─ dates_of_birth: ["1960-03-15"] ├─ has_pep: true ├─ has_sanctions: true ├─ has_watchlist: false └─ has_media: false
RiskProfiles are what matter - they represent actual matches from sanctions lists, PEP databases, or adverse media. Cases and Alerts are organizational containers.
Interpreting Risk Profiles
| Field | Purpose | How to Use |
|---|---|---|
matching_score |
Match confidence (0-100) | >80 = high confidence, <50 = likely false positive |
matching_name |
Name from watchlist | Compare to your entity name to verify match quality |
aml_types |
Types of risks found | "sanction" = reject, "pep" = enhanced due diligence |
has_sanctions |
Quick sanction check | If true, typically auto-reject |
has_pep |
Quick PEP check | If true, trigger enhanced due diligence |
dates_of_birth |
Known DOBs for match | Cross-reference with your entity's DOB |
also_known_as |
Aliases/AKAs | Check if any match your entity's variations |
Case Management
Cases are review containers created when screening finds potential matches. Update case status and decisions as you review the associated risk profiles.
Test API keys can only access cases created from test screenings. Live API keys can only access cases from live screenings. This ensures complete data segregation between test and production environments.
Get Case Details
/api/v1/cases/{case_id}/
Retrieve full details for a specific case including status, decision, and assignment information.
Update Case
/api/v1/cases/{case_id}/
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
string | No | Case status: open, in_review, closed |
decision |
string | No | Final decision: no_risk, acceptable_risk, unacceptable_risk |
Batch Screening
Screen multiple existing entities in a single request (up to 10,000). Entities that have already been screened are automatically skipped. Perfect for periodic rescreening or bulk onboarding.
/api/v1/screening/batch/
Include an Idempotency-Key header (16-255 characters) to ensure batch screening jobs are processed exactly once. Use a unique key per batch operation.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
entity_ids |
array | Yes | Array of entity UUIDs to screen (1-10,000). Duplicate IDs are automatically removed. |
webhook_url |
string | No | Webhook URL for batch completion notification |
webhook_progress |
boolean | No | If true, receive progress webhooks during processing. Default: false |
Success Response (202 Accepted)
{
"job_id": "a8d8f1e2-4c5e-4f6a-9b2c-3d4e5f6a7b8c",
"status": "pending",
"total_count": 2,
"estimated_completion": "2026-01-28T10:45:00Z",
"webhook_url": "https://your-domain.com/webhooks/batch-complete",
"skipped_count": 1,
"skipped_entity_ids": ["770e0622-e41d-34f6-c938-648877662222"],
"message": "Screening 2 entities. Skipped 1 already screened."
}
Error Response (409 Conflict)
{
"error": "All entities already screened",
"already_screened_count": 3,
"already_screened_entity_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"660f9511-f30c-23e5-b827-537766551111",
"770e0622-e41d-34f6-c938-648877662222"
]
}
Check Batch Status
Poll the batch job status endpoint to check progress:
/api/v1/screening/batch/{job_id}/
import time
job_id = "a8d8f1e2-4c5e-4f6a-9b2c-3d4e5f6a7b8c"
# Poll until complete
while True:
response = requests.get(
f'https://api.kycgenie.com/api/v1/screening/batch/{job_id}/',
headers={'Authorization': 'Bearer your_api_key'}
)
status_data = response.json()
print(f"Progress: {status_data['progress_percentage']}%")
print(f"Processed: {status_data['processed_count']}/{status_data['total_count']}")
if status_data['status'] in ['completed', 'failed']:
break
time.sleep(5) # Wait 5 seconds before next poll
const jobId = 'a8d8f1e2-4c5e-4f6a-9b2c-3d4e5f6a7b8c';
// Poll until complete
while (true) {
const response = await fetch(
`https://api.kycgenie.com/api/v1/screening/batch/${jobId}/`,
{ headers: { 'Authorization': 'Bearer your_api_key' } }
);
const statusData = await response.json();
console.log(`Progress: ${statusData.progress_percentage}%`);
console.log(`Processed: ${statusData.processed_count}/${statusData.total_count}`);
if (['completed', 'failed'].includes(statusData.status)) {
break;
}
await new Promise(r => setTimeout(r, 5000)); // Wait 5 seconds
}
Batch Status Response
{
"job_id": "a8d8f1e2-4c5e-4f6a-9b2c-3d4e5f6a7b8c",
"status": "processing",
"total_count": 3,
"processed_count": 2,
"failed_count": 0,
"progress_percentage": 66.67,
"created_at": "2026-01-28T10:30:00Z",
"started_at": "2026-01-28T10:30:15Z",
"completed_at": null,
"webhook_url": "https://your-domain.com/webhooks/batch-complete"
}
Batch screening processes approximately 2 entities per minute. Maximum batch size is 10,000 entities. For larger datasets, split into multiple batches.
Webhooks
Screening operations are asynchronous. Configure webhooks to receive real-time notifications when screenings complete, rather than polling for results.
Configuration
Webhooks can be configured in three ways:
- Request-level: Include
webhook_urlin your screening request (highest priority) - Action-level: Set a default webhook URL in your settings for screening actions
- No webhook: Poll the results endpoints manually
Webhook Events
| Event Type | When Triggered | Typical Delay |
|---|---|---|
screening.completed |
Single entity screening finishes | 1-10 seconds |
batch.completed |
Batch screening job finishes | 1-10 seconds per entity |
batch.progress |
Batch progress update (if webhook_progress=true) | Every 10 entities |
Webhook Payload - screening.completed
{
"event_type": "screening.completed",
"timestamp": "2026-01-22T15:30:45Z",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"screening_id": "scr_abc123def456",
"entity_id": "660f9511-f30c-23e5-b827-537766551111",
"entity_name": "ACME Corporation Ltd",
"entity_type": "company",
"status": "completed",
"risk_level": "medium",
"risk_score": 6.5,
"match_count": 3,
"screened_at": "2026-01-22T15:30:45Z",
"risk_profiles_count": 3
}
}
Webhook Security
All webhook requests include an X-Webhook-Signature header
in the format sha256={signature}. Verify this HMAC-SHA256 signature using your subscription secret key to ensure requests are authentic.
Webhook Headers
| Header | Description | Example |
|---|---|---|
X-Webhook-Signature |
HMAC-SHA256 signature for verification | sha256=abc123... |
X-Webhook-Event |
Event type triggering the webhook | screening.completed |
X-Webhook-Delivery-ID |
Unique delivery ID for this webhook | 550e8400-e29b... |
X-Webhook-Attempt |
Attempt number (1-5) | 1 |
Webhook Retry Policy
If your webhook endpoint returns a non-2xx status code or times out:
- Retry 1: After 1 minute
- Retry 2: After 5 minutes
- Retry 3: After 15 minutes
- Retry 4: After 1 hour
- Retry 5: After 4 hours
- After 5 failures: Webhook is marked as failed permanently
Your webhook endpoint must respond within 30 seconds. For long-running processing, return 200 immediately and handle the work asynchronously.