Developer API
Screen individuals and entities against sanctions, PEP, and adverse-media sources, verify identities, and score them against your risk matrix — directly from your own systems.
Getting started
Server-to-server integrations use a single API-key-authenticated route, POST /countcomply/external, separate from the JWT-authenticated route the dashboard itself uses. It's meant for headless/backend use only — the route carries no CORS handling, so it can't be called from browser-side JavaScript on any origin.
- Sign in to the dashboard and open Settings → Integrations.
- Issue an API key. The full secret is shown once — store it in your own secrets manager, not source control.
- Send it as
Authorization: Bearer <your-api-key>on every request below.
An API key represents your organization as a whole (equivalent to your account's master role), not an individual user — there's no per-user key.
Base URL & authentication
Every call is a POST to the same URL — the JSON body's processingCode field selects the action:
POST https://www.countcomply.com/countcomply/external Content-Type: application/json Authorization: Bearer <your-api-key>
Only the processing codes documented on this page are reachable on this route — bulk screening and any account/organization administration are dashboard-only and will be rejected with responseCode 555.
Request & response shape
Every request body:
{
"processingCode": "REQUEST_SANCTION",
"...action-specific fields": "..."
} Every response is HTTP 200 with the real outcome inside the body:
{
"responseCode": "000",
"responseMessage": "Human-readable outcome",
"results": [ /* present on lookup (GET_*) calls — always an array */ ]
} Treat responseCode !== "000" as failure and surface responseMessage — don't branch UI logic on the exact code except 993 (quota exhausted). On a GET_* call, an empty results array with responseCode: "000" means "not found," not an error.
Response codes
| Code | Meaning |
|---|---|
000 | Success (including "no records found" on a GET_*) |
993 | Screening/risk-scoring quota exhausted, or no active subscription |
999 | Validation error or generic failure — responseMessage carries the reason |
555 | Unsupported processingCode for this route, or the request timed out server-side |
901 | Malformed request — empty body, wrong HTTP method, or missing API key |
Sanctions screening
AI-adjudicated screening against sanctions watchlists.
REQUEST_SANCTION
Accepted immediately and screened in the background — poll GET_SANCTION_REQUEST for the outcome. fullName is the only field you need to send:
curl -X POST https://www.countcomply.com/countcomply/external \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{
"processingCode": "REQUEST_SANCTION",
"fullName": "Jane Doe"
}'Response (immediate):
{
"responseCode": "000",
"sanctionRequestCode": "SNR-XXXXXXXXXXXX",
"status": "PROCESSING"
} Sharper subject data narrows the match further, if you have it — all optional: dateOfBirth, nationality, referenceNumber (your own tracking id), subjectType ("INDIVIDUAL" or "ENTITY"), similarityThreshold (1–100, default 60).
GET_SANCTION_REQUEST
Poll this until status reaches COMPLETED or FAILED (typically well under a minute, occasionally longer under CPU-bound adjudication load — treat anything stuck past ~20 minutes as failed).
{
"processingCode": "GET_SANCTION_REQUEST",
"sanctionRequestCode": "SNR-XXXXXXXXXXXX"
}{
"responseCode": "000",
"results": [{
"sanctionRequestCode": "SNR-XXXXXXXXXXXX",
"fullName": "Jane Doe",
"status": "COMPLETED",
"degraded": false,
"cached": false,
"dateOfBirth": "1980-01-15",
"nationality": "Example country",
"results": [{
"sanctionResultCode": "SRS-XXXXXXXXXXXX",
"sanctionListCode": "SNL-XXXXXXXXXXXX",
"matchedName": "Jane A. Doe",
"matchScore": 92.5,
"aiConfidence": 97,
"matchType": "PARTIAL",
"outcome": "FLAGGED",
"matchAgreements": "dateOfBirth,nationality",
"matchConflicts": null,
"matchedAliases": "Jane Doe",
"matchedDateOfBirth": "1980-01-15",
"matchedNationality": "Example country",
"matchedIdentificationNumbers": "Passport: EXAMPLE123",
"matchedSourceReference": "source-entry-123",
"matchedListType": "INDIVIDUAL",
"matchedListSource": "Example sanctions source",
"matchedRemarks": "Example source listing information.",
"remarks": "Example source listing information.",
"createdAt": "2026-09-11T10:00:00"
}]
}]
}degraded: true means the AI adjudicator was unreachable and the result is provisional — treat it as lower-confidence and worth a manual look. Read every entry in results[0].results: this is the same complete result array used by the dashboard results pane, including matched identity and source details. Fields missing from the source are null. Request-level nationality and date of birth describe your submitted subject; the matched* fields describe the watchlist entry. A completed clean sanctions/PEP screening has a NO_MATCH result row. Check responseCode and status before interpreting results; a failed lookup is not a clean screening. GET_SANCTION_REQUESTS (plural, no per-request code) lists request summaries for your organization, newest first; use the singular lookup for full result details.
PEP screening
Identical request/response shape to sanctions screening above, just against politically-exposed-person lists instead. Swap the processing codes and field prefix:
| Sanctions | PEP equivalent |
|---|---|
REQUEST_SANCTION | REQUEST_PEP |
GET_SANCTION_REQUEST | GET_PEP_REQUEST (request: pepRequestCode) |
GET_SANCTION_REQUESTS | GET_PEP_REQUESTS |
sanctionRequestCode | pepRequestCode |
PEP results use pepResultCode and pepListCode, and include matchedPositionHeld and matchedArea instead of sanctions' matchedListType. All other matched identity and source fields above apply.
Adverse media screening
Retrieves and AI-adjudicates negative-news coverage against a fixed 16-category taxonomy (financial crime, corruption, fraud, sanctions evasion, and related categories).
REQUEST_ADVERSE_MEDIA
Same idea as sanctions above — fullName is the only field you need to send:
curl -X POST https://www.countcomply.com/countcomply/external \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{
"processingCode": "REQUEST_ADVERSE_MEDIA",
"fullName": "Jane Doe"
}' Always accepted immediately (status: "PROCESSING") and runs in the background. Poll GET_ADVERSE_MEDIA_REQUEST for the outcome, or add webhookUrl (a publicly-reachable HTTPS/HTTP endpoint — loopback/private/ link-local addresses are rejected) to also receive a callback; the two aren't mutually exclusive. Optional too: referenceNumber (your own tracking id) and confidenceThreshold (1–100, default 60).
Webhook delivery
Once the request settles (COMPLETED or FAILED), we POST the full outcome to webhookUrl, retrying up to 3 times (2s/5s backoff) on delivery failure:
{
"adverseMediaRequestCode": "ADM-XXXXXXXXXXXX",
"status": "COMPLETED",
"hit": true,
"degraded": false,
"matchCount": 1,
"cached": false,
"hits": [{
"title": "Executive charged in fraud investigation",
"url": "https://example-news.com/article",
"domain": "example-news.com",
"publishedAt": "2026-03-11",
"categories": ["fraud"],
"caseStatus": "CHARGED",
"confidence": 88,
"reasoning": "Headline names the subject and cites the charge directly."
}]
} A webhook that never arrives doesn't mean anything failed server-side — poll GET_ADVERSE_MEDIA_REQUEST as the reliable fallback. GET_ADVERSE_MEDIA_REQUESTS lists every request for your organization.
Identity verification
National ID / Alien ID verification. Unlike screening, this is fully synchronous — one call, one answer, no polling.
REQUEST_IDENTITY_VERIFICATION
curl -X POST https://www.countcomply.com/countcomply/external \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{
"processingCode": "REQUEST_IDENTITY_VERIFICATION",
"idType": "NATIONAL_ID",
"idNumber": "12345678",
"countryCode": "KE"
}'{
"responseCode": "000",
"results": [{
"identityVerificationRequestCode": "IDV-XXXXXXXXXXXX",
"status": "COMPLETED",
"success": true,
"valid": true,
"countryCode": "KE",
"firstName": "Jane",
"lastName": "Doe",
"gender": "F",
"dateOfBirth": "1985-04-12",
"citizenship": "KE"
}]
}countryCode is required and must currently be "KE" — Kenya is the only jurisdiction this integration verifies against. Billed per call at a flat rate (identical ID looked up twice is served from cache at no extra charge, invisibly — there is no cached field to check). Requires identity verification to be enabled on your plan. GET_IDENTITY_VERIFICATION_REQUEST / GET_IDENTITY_VERIFICATION_REQUESTS are read-only history lookups.
Risk scoring
Scores a subject against your organization's weighted risk matrix (or the platform default if you haven't customized one).
GET_RISK_MATRIX
Fetch the factor/weight/band definition currently in effect for your organization — useful for building a matching input form.
{ "processingCode": "GET_RISK_MATRIX" }CALCULATE_RISK_SCORE
One-shot: scores the subject and records the request/result in a single call. Only send the fields your matrix's factors actually use (see GET_RISK_MATRIX).
curl -X POST https://www.countcomply.com/countcomply/external \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{
"processingCode": "CALCULATE_RISK_SCORE",
"fullName": "Jane Doe",
"customerType": "Individual",
"occupation": "Accountant",
"country": "KE",
"screeningResult": "Clear",
"pepStatus": "No",
"channel": "Online",
"transactionVolume": 5000,
"ageYears": 3
}'{
"responseCode": "000",
"riskScoringRequestCode": "RSR-XXXXXXXXXXXX",
"riskScoringResultCode": "RSL-XXXXXXXXXXXX",
"totalScore": 24,
"riskLevel": "LOW",
"scoreBreakdown": {
"riskMatrixCode": "RMX-XXXXXXXXXXXX",
"factors": [
{ "key": "customer_type", "label": "Customer Type", "valueSupplied": "Individual", "score": 1, "weight": 0.15, "weightedContribution": 0.15 }
]
}
} Prefer a two-step flow instead? REQUEST_RISK_SCORING creates a bare request row you can score later. GET_RISK_SCORING_REQUEST / GET_RISK_SCORING_REQUESTS read history.
Usage & quota
GET_PARTNER_USAGE
Check remaining allowance before you screen — a sanctions or PEP request costs 20 tokens, adverse media 100, risk scoring 20 (identity verification is billed separately, not counted here).
{ "processingCode": "GET_PARTNER_USAGE" }{
"responseCode": "000",
"subscribed": true,
"planName": "Professional",
"allowance": 50000,
"used": 12400,
"remaining": 37600,
"unlimited": false,
"blocked": false
} If blocked is true (or a screening call returns 993), the next screening/risk-scoring attempt will be refused until the allowance resets or your plan changes.
