Sanction
Sanctions Screening API

Sanctions Screening API

Screens a person, organization, crypto wallet, bank account or transaction against sanctions, PEP and other risk lists. It returns matches, not verdicts — the decision is yours.

Sanctions screening integration flowFlow: your server obtains a short-lived access token with an Ed25519 signature, calls the screening endpoint with it and receives a list of matches. With no match your flow continues; with a match the decision is yours and the subject can be registered for ongoing monitoring.WITH BEAREREMPTYMATCHYour serverEd25519 private keyPOST /api/v1/auth/tokensigned requestAccess tokenshort-lived BearerPOST /api/v1/sanction/screening/name · wallet · account · transactionmatches[]score · hitType · listFlow continuesYour decisionblock / reviewLEGENDYOUR SIDESCREENING CALLRESPONSE
Lunixi does not decide for you: the screening endpoint returns matches and scores, never a "block" verdict. What you do with a given threshold and hit type is your policy — and that separation is what you defend in an audit.

Authentication

The screening endpoints require a short-lived access token, obtained with an Ed25519 signature:

POST https://api-gateway.lunixi.com/api/v1/auth/token
HeaderRequiredDescription
X-Key-Id✅Your public key id (kid)
X-Signature✅Ed25519 signature (Base64)
X-Date✅ISO-8601 request timestamp
X-Nonce✅Unique request nonce
Digest—SHA-256 body digest (Base64); required when there is a body

Send the returned token as Authorization: Bearer … on screening calls.

⚠️

Your key must carry the sanction:view_results scope; a token minted from a key without it is rejected on the screening endpoints. Canonical string and error codes: Authentication.

Screening endpoints

Four subject types, four endpoints:

POST /api/v1/sanction/screening/name          # person or organization
POST /api/v1/sanction/screening/wallet        # crypto wallet address
POST /api/v1/sanction/screening/account       # bank account / IBAN
POST /api/v1/sanction/screening/transaction   # transaction (with its parties)

Name, wallet and transaction screening, and public procurement ban screening, run against the active list publication: every screening pins the publication in force at that moment and tells you in the response which one it read. What a list publication is: Sanctions Screening.

Name screening body

FieldTypeRequiredNotes
namestring✅The name to screen
subjectTypestring—person | organization
countrystring—ISO 3166-1 alpha-2
dateOfBirthstring—ISO-8601; cuts false positives sharply on person screening
identifiersarray—Disambiguators such as national id, tax number, passport
riskDomainsstring[]—Narrows the screening to these risk domains: sanctions, pep, rca, wanted, regulatory, debarment, ownership, vessel, securities. When omitted, every risk domain is screened
modestring—sanctions_only | sanctions_pep | full_risk — a preset set of risk domains (see AML Screening). Sent together with riskDomains, their intersection is screened
thresholdnumber—Minimum match score
customerReferencestring—Your own record id (written to the audit trail)
curl -X POST "https://api-gateway.lunixi.com/api/v1/sanction/screening/name" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mehmet Öz",
    "subjectType": "person",
    "country": "TR",
    "dateOfBirth": "1980-04-12",
    "riskDomains": ["sanctions", "pep", "rca"],
    "threshold": 0.85,
    "customerReference": "cust_84213"
  }'
⚠️

An unknown mode or risk domain, or an empty intersection of mode and riskDomains, is refused with sanction.screening.scope_invalid (400). An invalid domain is never silently dropped — if it were, a domain nobody screened would read as "no match".

Response

The response comes in the standard response envelope; the screening fields are under data.

{
  "status": "success",
  "code": "sanction.screen_name.completed",
  "message": "…",
  "data": {
    "latencyMs": 128,
    "screeningRequestId": "…",
    "publicationId": "…",
    "publicationRevision": 34,
    "coverageStatus": "COMPLETE",
    "evidenceRef": "…",
    "screenedRiskDomains": ["sanctions", "wanted", "regulatory", "debarment", "pep", "rca"],
    "uncoveredRiskDomains": [],
    "matches": [
      {
        "hitId": "…",
        "entityId": "…",
        "matchedName": "Mehmet Oz",
        "score": 0.91,
        "hitType": "probable",
        "listCode": "OFAC SDN",
        "sourceListName": "OFAC SDN",
        "recordType": "person",
        "riskDomains": ["sanctions"],
        "reason": "PROBABLE_MATCH",
        "explanationJson": "{…}",
        "lists": [
          {
            "datasetCode": "OFAC",
            "displayName": "OFAC SDN",
            "datasetKind": "SANCTIONS",
            "datasetVersionId": "…",
            "subLists": [],
            "programs": ["…"],
            "entityIds": ["…"],
            "listings": [
              { "subList": "", "label": "…", "program": "…", "reference": "…", "listedOn": "…" }
            ]
          },
          {
            "datasetCode": "UN",
            "displayName": "…",
            "datasetKind": "SANCTIONS",
            "datasetVersionId": "…",
            "subLists": [],
            "programs": ["…"],
            "entityIds": ["…"],
            "listings": []
          }
        ]
      }
    ],
    "datasetFreshnessJson": "{…}"
  },
  "statusCode": 200,
  "requestId": "…"
}
FieldMeaning
matchesAn empty array means clear only when status is "success" — and, when coverageStatus is not COMPLETE, only for the domains that were screened. See When screening cannot run
scoreSimilarity between 0 and 1; anything under threshold is dropped
hitTypeexact | probable | possible
riskDomainsRisk domains of the matched person or organization (sanctions, pep, wanted …)
listsEvery list the match appears on — list code and name, the list version read, programs and sub-lists, listing records (listings)
sourceListNameThe list of the match's representative record. Read lists for all of them
screeningRequestIdThe screening's id; use it for screening history and report requests. The top-level requestId identifies the HTTP request, not the screening
publicationId · publicationRevisionThe list publication the screening read and its revision at screening time
coverageStatusCOMPLETE | PARTIAL | DEGRADED — below
screenedRiskDomainsThe risk domains requested
uncoveredRiskDomainsRequested risk domains no list in the publication covers
evidenceRefId of the screening's immutable evidence record; use it in audits and disputes
datasetFreshnessJsonWhich vintage of data you screened against. "We screened PEP" alone is not defensible in an audit

Merged match

When the same person or organization is on several lists you get one match: its score is the highest of the lists, and every list the subject is on is in lists. You do not get three rows for OFAC, UN and EU on the same person; you get one row with three lists.

Two records on the same list are separate matches even when their names are identical: two records a publisher lists under the same name are two different people or organizations.

Coverage status

coverageStatusMeaningWhen there is no match
COMPLETEEvery requested risk domain was screenedClear
PARTIALAt least one requested risk domain has no list in the publication; those domains are in uncoveredRiskDomainsClear only for the screened domains — not for uncoveredRiskDomains
DEGRADEDThe candidate search that finds differently spelled names was unavailable, or the candidate window filled up on a very common name; similarly spelled names may have been missedDo not treat it as a full screening; run it again

Replaying a screening made before the switch to list publications with the same idempotency key returns publicationId, publicationRevision, coverageStatus and evidenceRef as null.

⚠️

An empty datasetFreshnessJson means the freshness manifest could not be produced. Word your compliance statement conservatively — you cannot claim a specific list vintage.

When screening cannot run

If no active list publication can be pinned, the screening is not run and there is no fallback to older data. The response carries status: "failure" and HTTP 503:

Endpointcode
Name screeningsanction.screen_name.failed
Wallet screeningsanction.wallet.failed
Transaction screeningsanction.transaction.failed
Public procurement ban screeningsanction.public_procurement.failed
🚫

A failed screening must never be read as "no match". Hold the customer record or transaction as "not screened", then retry the screening or send it to review. An empty matches list only means something when status is "success".

Wallet screening

POST /api/v1/sanction/screening/wallet
FieldTypeRequiredNotes
walletAddressstring✅The address to screen
chainstring—Network (e.g. ethereum, bitcoin, tron, solana)
modestring—wallet_sanctions (default) | wallet_risk | wallet_full_aml — the wider modes add exposure and crypto-intelligence hits
customerReferencestring—Your own record id

The address is looked up by exact match against the crypto wallet addresses carried by the lists in the active publication; there is no similarity search. Letter case is ignored only where it carries no information:

Address formatComparison
EVM hex (0x…)Case-insensitive
Prefixed bech32 (bc1…, ltc1… etc.)Case-insensitive
Prefixed CashAddrCase-insensitive
Others (e.g. Bitcoin legacy, Tron, Solana)Case-sensitive — send the address exactly as given

Matching is by address, not by network label: the same EVM address is the same address on every EVM network. The network the list states is returned separately in listedChains.

{
  "status": "success",
  "code": "sanction.wallet.completed",
  "data": {
    "address": "0x…",
    "chain": "ethereum",
    "isSanctioned": true,
    "riskScore": 100,
    "riskLevel": "…",
    "publicationId": "…",
    "publicationRevision": 34,
    "coverageStatus": "COMPLETE",
    "evidenceRef": "…",
    "publicationHits": [
      {
        "hitId": "…",
        "entityId": "…",
        "entityName": "…",
        "entityType": "ORGANIZATION",
        "hitType": "exact",
        "score": 100,
        "riskCategory": "sanctions",
        "riskDomains": ["sanctions"],
        "sourceListName": "OFAC SDN",
        "lists": [ { "datasetCode": "OFAC", "displayName": "OFAC SDN", "…": "…" } ],
        "listedAddress": "0x…",
        "listedChains": ["ETH"],
        "caseRule": "EVM_HEX"
      }
    ],
    "hits": []
  },
  "statusCode": 200
}
FieldMeaning
publicationHitsListed parties in the publication that hold the address. A party on several lists is one hit, with all its lists in lists
riskCategoryThe party's strictest risk domain
listedAddress · listedChainsThe address as the list writes it, and the network or currency the list states
caseRuleThe comparison rule applied: EVM_HEX | BECH32 | CASHADDR | CASE_SENSITIVE
hitsYour own wallet list, exposure records and crypto intelligence. List-publication hits are never in this field
isSanctionedtrue when a party in the sanctions domain holds the address

When a party in the sanctions domain holds the address, isSanctioned is true and the score is 100. A party in any other risk domain (e.g. wanted, regulatory) comes back with score 90 and is a hit that needs review. If no list publication can be pinned, wallet screening does not run either (503, above).

Account screening

POST /api/v1/sanction/screening/account

Sanctions lists do not publish IBANs or bank account numbers. Account screening therefore screens your own account list (blacklist) and account risk data, not the list publication; its response has no publication fields. Screen the account holder's name separately with name screening.

Transaction screening

POST /api/v1/sanction/screening/transaction

All parties of a transaction are screened against the same list publication. The response adds publicationId, publicationRevision and coverageStatus; coverageStatus is the weakest coverage among the parties. Per-party evidence, coverage and list detail are in resultsJson. When no party was screened these fields are empty.

Public procurement ban screening

POST /api/v1/sanction/intelligence/public-procurement-ban

Screens a person or organization against the debarment (debarment) lists of the active publication, by name and Turkish TCKN/VKN.

FieldTypeRequiredNotes
entityNamestring—Person or organization name
tckn · vknstring—Matched exactly; an equal TCKN/VKN produces a match even when the name differs
entityTypestring—Person or organization
onlyActiveboolean—Only bans currently in force
limitnumber—Maximum number of hits
customerReferencestring—Your own record id
productChannelstring—Which product the call came from (reporting)

At least one of entityName, tckn and vkn is required; with none of them the request is refused with sanction.screening.subject_invalid (400).

{
  "status": "success",
  "code": "sanction.public_procurement.completed",
  "data": {
    "matched": false,
    "riskScore": 0,
    "decision": "…",
    "hits": [],
    "publicationId": "…",
    "publicationRevision": 34,
    "coverageStatus": "PARTIAL",
    "evidenceRef": "…",
    "screenedRiskDomains": ["debarment"],
    "searchedLists": [
      { "datasetCode": "…", "displayName": "…", "datasetVersionId": "…" }
    ],
    "uncoveredSources": [
      { "datasetCode": "TR_PUBLIC_PROCUREMENT_BAN", "displayName": "…", "reason": "NOT_PUBLISHED" }
    ]
  },
  "statusCode": 200
}
FieldMeaning
searchedListsThe debarment lists the screening read
uncoveredSourcesSources expected to be screened but absent from the publication
coverageStatusPARTIAL while any source is uncovered
hits[].matchBasisWhat the match rests on (e.g. tckn, vkn, name)
hits[].listsThe lists the hit appears on (same structure as name screening)
⚠️

Turkish public procurement bans (KİK) are not part of this screening. The list has no official machine-readable source, so it is not screened; the response reports TR_PUBLIC_PROCUREMENT_BAN in uncoveredSources with PARTIAL coverage. No match is not a clean result for public procurement bans in Türkiye — you need to check EKAP separately.

Screening history

GET /api/v1/sanction/screening/history

Returns earlier screenings. This answers "when and in which mode did we screen this customer" in an audit — fill customerReference and you will find it here.

Ongoing monitoring

A one-off screening does not warn you when the lists change. Register the subject for monitoring:

POST /api/v1/sanction/monitoring/register     # register a subject
GET  /api/v1/sanction/monitoring/alerts       # new match alerts
GET  /api/v1/sanction/monitoring/entities     # monitored subjects
GET  /api/v1/sanction/monitoring/matches      # matches raised by monitoring
GET  /api/v1/sanction/monitoring/dataset-freshness   # dataset freshness

If you screen at onboarding but do not register for monitoring, you will not hear about it when that customer is listed the next day. Onboarding screening and ongoing monitoring are two separate obligations.

Register body

{
  "externalCustomerId": "cust_9001",
  "subjectType": "person",
  "profileJson": "{\"name\":\"Ahmet Yilmaz\",\"countryCode\":\"TR\",\"dateOfBirth\":\"1980-01-31\",\"identifiers\":[{\"type\":\"tckn\",\"value\":\"12345678901\"}]}",
  "frequency": "DAILY",
  "riskDomainScope": ["sanctions", "pep"]
}
FieldMeaning
externalCustomerIdYour reference for the customer. It comes back in alerts and webhooks; registering the same reference again updates the profile.
subjectTypeperson or organization.
profileJsonThe subject's profile as a JSON string: name (required), countryCode, dateOfBirth, identifiers[] (type, value). Re-screenings use this profile; identifiers sharpen the match.
frequencyDAILY, WEEKLY or MONTHLY; daily when omitted. When the list publication changes, affected subjects are re-screened regardless of the frequency.
riskDomainScopeRisk domains to monitor (sanctions, pep, rca, wanted, regulatory, debarment). The sanctions domain when omitted.

Response: { "success": true, "monitoredEntityId": "<uuid>" }. The call works with an API key (sanction:monitoring scope) or a panel session; the Sanctions → Monitoring → Register for Monitoring card in the panel uses the same endpoint.

Monitoring webhooks

When a new match is found, a sanction.monitoring.match_found event is delivered to your endpoint; adverse media monitoring raises sanction.monitoring.adverse_media_found. The envelope, signature and retry rules are shared by all products — see Webhooks. Subscribe by selecting the event type on your webhook endpoint.

{
  "type": "sanction.monitoring.match_found",
  "data": {
    "monitoredEntityId": "0b5d6a51-…",
    "externalCustomerId": "cust_9001",
    "subjectName": "Ahmet Yilmaz",
    "matchedName": "Ahmet YILMAZ",
    "sourceListName": "OFAC SDN",
    "riskDomains": ["sanctions"],
    "score": 93.4,
    "canonicalEntityId": "d5990c41-…",
    "datasetGeneration": "2026-10-19",
    "detectedAt": "2026-10-19T06:12:40.000Z",
    "caseId": null,
    "screeningRequestId": "…",
    "publicationId": "…",
    "publicationRevision": 40,
    "entityIds": ["d5990c41-…", "f89dd795-…"],
    "lists": [{ "datasetCode": "OFAC", "displayName": "OFAC SDN" }]
  }
}
FieldMeaning
monitoredEntityId, externalCustomerIdWhich monitoring record and which of your customers.
subjectName, matchedName, score, riskDomainsThe monitored profile, the listed name it matched, the score (0–100) and the risk domains.
sourceListName, lists[]The list presenting the match and every list the match appears on.
canonicalEntityId, entityIds[]The listed party and every record merged into the match; readable through the entity profile endpoint.
publicationId, publicationRevision, datasetGeneration, detectedAtWhich list publication the match was found against, and when. Keep them for your audit trail.
caseIdThe case id when a case was opened automatically.

One event is produced per match; a redelivery never produces a second notification. The same matches are listed by GET /api/v1/sanction/monitoring/matches.

Related endpoints

EndpointWhat it does
POST /api/v1/sanction/intelligence/adverse-mediaAdverse media screening
POST /api/v1/sanction/intelligence/public-procurement-banPublic procurement debarment (above)
GET /api/v1/sanction/intelligence/entity-searchEntity search
GET /api/v1/sanction/intelligence/entity-profiles/:entityIdEntity profile
GET /api/v1/sanction/intelligence/entity-graph/:entityIdRelationship graph
GET /api/v1/sanction/intelligence/indirect-exposure/:entityIdIndirect exposure
GET /api/v1/sanction/intelligence/ownership-aggregation/:entityIdOwnership aggregation (50% rule)
POST /api/v1/sanction/blacklistAdds an entry to your own blacklist. Your key must carry the sanction:add_list_entry scope; the entry is written to your list only
GET /api/v1/sanction/blacklistLists your own blacklist (sanction:view_results)