Screen API
Real-time sanctions, PEP, and transaction screening for regulated payment flows — one call at onboarding, one call per payment. Every decision returns machine-readable evidence and a durable audit ID.
The API is JSON over HTTPS. Two calls cover most integrations: POST /v1/screen/entity for a party (a customer at onboarding, a beneficiary), and POST /v1/screen/transaction for a payment (both parties + corridor + amount). Both return a single decision — clear alert hold review — with the matches and rationale behind it.
Authentication
Every request carries a platform-issued API key in the Authorization header as a bearer token. Keys are environment-scoped: ssk_test_… for sandbox, ssk_live_… for production. Never expose a key in a browser or mobile client — call from your server.
Authorization: Bearer ssk_live_YOUR_SECRET_KEY
An unrecognized, revoked, or wrongly-scoped key fails closed with 401 / 403. The screening scope is screen:entity (it authorizes both the entity and transaction endpoints).
Quickstart
Screen a person by name. A clean name returns clear; a designated name returns hold with the matching lists.
# Screen a customer at onboarding
curl https://api.celmus.com/v1/screen/entity \
-H "authorization: Bearer $SCREEN_API_KEY" \
-H "content-type: application/json" \
-d '{
"subject": { "entity_type": "individual", "name": "Jane Ordinary" },
"options": { "tenant_jurisdiction": "CA" }
}'
Screen an entity
Screen a single party — an onboarding customer, a beneficiary, a counterparty — against sanctions, PEP, and (where an ownership graph is loaded) OFAC-50% derived blocking. Pass options.tenant_jurisdiction so the PEP determination can resolve foreign vs. domestic.
Request
| Field | Type | Notes |
|---|---|---|
subject.entity_type | enum | individual · organization · vessel · aircraft · crypto_address |
subject.name | string | Required. Any script — Cyrillic, Arabic, CJK, etc. are romanized and matched. |
subject.dob | string | Optional. YYYY, YYYY-MM, or YYYY-MM-DD (partial dates supported). |
subject.nationalities | string[] | ISO 3166-1 alpha-2. |
subject.addresses | object[] | { country, city?, postal_code?, lines?[] } |
subject.identifiers | object[] | { id_type, issuing_country?, value } — passport, national_id, tax_id, lei, … |
options.tenant_jurisdiction | string | Your own jurisdiction (ISO-2). Resolves the PEP foreign/domestic + 5-year clock. |
options.determination_trigger | enum | account_opening · periodic_review · trigger_fact |
Response
{
"audit_id": "7c1f…", // durable evidence reference
"decision": "hold", // clear | alert | hold | review
"matches": [{
"entity_id": "…",
"canonical_name": "Vladimir PUTIN",
"score_adjusted": 98,
"action": "block_eligible_hold",
"restriction": "blocked",
"programs": ["UKRAINE-EO14024"],
"lists": [{ "source_short_code": "OFAC-SDN", "score": 98 }],
"pep": { "is_pep": true, "classification": "foreign_pep", "edd_required": true },
"derived": null // present for OFAC-50% derived blocks
}],
"coverage_warnings": [], // non-empty = screened against reduced coverage
"capsule": { "capsule_id": "…", "state": "pending" },
"compliance_profiles": [{ "profile_code": "CA-FINTRAC", "version": 1 }]
}
A review decision is not a match — it's an explicit refusal to assert the party is clear (a verification disagreement, or a sanctions source was stale/unavailable). Treat review like hold: stop and have a human look. A clear is the only verdict that moves money without review.
Screen a transaction
Screen a payment: both parties are run through the entity engine, then transaction-level rules apply — ministerial directives (a transfer to/from a directive jurisdiction), travel-rule completeness, and the PEP threshold-transaction trigger. Amounts are normalized to the threshold currency before comparison.
Request
{
"transaction": {
"reference": "rmt_9f2c",
"amount": 2500, "currency": "CAD",
"direction": "outbound", // inbound | outbound | internal
"rail": "eft", // eft | wire | vc | prepaid | domestic
"origin_country": "CA", "destination_country": "PH",
"originator": { "name": "Juan Dela Cruz", "account": "…", "address_present": true },
"beneficiary": { "name": "Maria Santos", "addresses": [{ "country": "PH" }] }
},
"options": { "tenant_jurisdiction": "CA" }
}
For rail: "vc" add vasp_identified + wallet_present on each party (IVMS-101). For non-USD amounts pass transaction.fx_rates (units of USD per 1 unit, e.g. { "EUR": 1.08 }) so thresholds compare correctly.
Response (extra fields)
"decision": "hold",
"parties": [{ "role": "beneficiary", "decision": "clear", "is_pep": false }],
"ministerial_directives": [{ "country": "IR", "directive": "IRAN-2020", "leg": "destination" }],
"travel_rule": { "applicable": true, "complete": false, "missing": ["beneficiary.account"] },
"pep_triggers": [], // intl EFT/VC ≥ $100k involving a PEP
"fx": { "normalized": { "amount": 1840, "currency": "USD" }, "approximate": false }
Decisions & actions
The top-level decision is the worst outcome across all matches and rules. Each match also carries an action describing what that specific hit warrants.
| Decision | Meaning |
|---|---|
| clear | No hit, coverage confirmed. Safe to proceed automatically. |
| alert | A discretionary hit (PEP, adverse media) or incomplete travel-rule data. Review, don't block. |
| hold | A sanctions / asset-freeze hit or a ministerial directive. Stop the payment, route to a human. |
| review | Could not confirm a clear (verification disagreement or reduced coverage). Treat as hold. |
| Match action | Restriction it reflects |
|---|---|
block_eligible_hold | blocked · directive_restricted · correspondent_restricted — funds-restricting. |
alert_only | PEP, watchlist, or an opaque-ownership flag — surfaces, never auto-blocks. |
risk_signal | Adverse media, jurisdiction risk, sub-50% control risk — informational. |
The pep block
Present on a match when the party is a politically exposed person or a family member / close associate of one. It carries the FINTRAC determination — never merge it with the sanctions score in your UI; it's a separate obligation (enhanced due diligence, not a freeze).
| Field | Notes |
|---|---|
classification | foreign_pep (perpetual) · domestic_pep / hio (5-yr clock) · family_member · close_associate |
status | active · within_5y · lapsed · perpetual |
edd_required | Whether enhanced due diligence is owed. |
determination_pending | true when tenant_jurisdiction was omitted — foreign/domestic couldn't be finalized, so it's never silently cleared. |
The derived block
Present when a party is not directly listed but inherits a restriction because blocked persons own ≥ 50% of it (the OFAC 50% Rule). Populated once an ownership graph is loaded for your program.
| Field | Notes |
|---|---|
aggregate_pct | Aggregate blocked ownership (≥ 50 = derived block, inclusive). |
chain | The ownership path behind the derivation, for the rationale. |
opaque_intermediary | true = an unresolved owner could tip it over 50% — flagged, never a clean pass. |
control_risk | Sub-50% blocked ownership with control indicia — a non-blocking signal. |
Errors
Errors use standard HTTP status codes with a application/problem+json body: { "title", "status", "detail" }. A screening failure (source unavailable, evidence unwritable) returns 503 and no decision — retry; never treat a failed screen as a clear.
| Status | Meaning |
|---|---|
400 | Malformed JSON body. |
401 | Missing or invalid API key. |
402 | Billing suspended — new screens paused (audit/export remain available). |
403 | No tenant context, or the key lacks the required scope. |
422 | Validation error — the detail lists each problem. |
503 | Screening or evidence recording unavailable — retry, do not proceed. |
Other endpoints
Beyond the two screening calls, the same key + platform expose the compliance workflow. Ask us for the full reference when you need them.
POST /v1/cdd/collect— beneficial-ownership capture (25%+ owners + control person + certification).POST /v1/314a/scan— FinCEN 314(a) segregated, ephemeral match (subjects never persisted).GET /v1/reporting/deadlines,/v1/reporting/events— the regulatory-reporting matrix (LPEPR, OFAC blocking, SAR/STR) with filing deadlines.GET /v1/audit/cases— exam-production case search over the WORM evidence log.GET /v1/coverage— coverage-truth: which lists, how fresh, what's unavailable.
Running in shadow mode
Call the API in parallel with your existing controls and log what celmus would decide — it never blocks a live payment. Keep the call server-side, time-bounded, and wrapped so an error or timeout is swallowed. When the comparison data holds, flip specific checks to enforcing. Start with a ssk_test_ key; the endpoints, shapes, and decisions are identical in production.