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
Authentication
Request/response
Response codes
Sanctions
PEP
Adverse media
Identity verification
Risk scoring
Usage & quota

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.

  1. Sign in to the dashboard and open Settings → Integrations.
  2. Issue an API key. The full secret is shown once — store it in your own secrets manager, not source control.
  3. 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

CodeMeaning
000Success (including "no records found" on a GET_*)
993Screening/risk-scoring quota exhausted, or no active subscription
999Validation error or generic failure — responseMessage carries the reason
555Unsupported processingCode for this route, or the request timed out server-side
901Malformed 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:

SanctionsPEP equivalent
REQUEST_SANCTIONREQUEST_PEP
GET_SANCTION_REQUESTGET_PEP_REQUEST (request: pepRequestCode)
GET_SANCTION_REQUESTSGET_PEP_REQUESTS
sanctionRequestCodepepRequestCode

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.