Version License MCP SDK TypeScript Bun
Install in Claude Desktop Install in Cursor Install in VS Code
Public Hosted Server: https://sanctions-screening.caseyjhand.com/mcp
Overview
Sanctions and export-control screening over eleven lists plus the GLEIF LEI registry, matched offline against a local mirror. Screen a name or identifier for potential watchlist hits, resolve a company to its LEI, and trace its accounting-consolidation relationships.
| Source codes | Lists |
|---|---|
ofac_sdn, ofac_consolidated | OFAC SDN and Consolidated |
eu, uk, un | EU, UK Sanctions List, UN Security Council |
bis_el, bis_dpl, bis_uvl, bis_meu | BIS Entity, Denied Persons, Unverified, Military End User |
state_dtc, state_acn | State ITAR Debarred, Nonproliferation Sanctions |
The six BIS/State lists come from trade.gov's keyless Consolidated Screening List (CSL) bulk download. Treasury CSL copies are skipped; direct OFAC provenance and grouping remain unchanged.
Important
A screening aid, not a compliance determination. Every result is a potential match with a transparent score and source provenance, never a verdict. A hit is a candidate to verify against the official source; an empty result is never a clearance. Sanctions compliance needs human review and a qualified determination. This server feeds that process and does not perform it.
Tools
| Tool | Description |
|---|---|
sanctions_screen_name | Screen a person, company, vessel, or aircraft name against every loaded watchlist at once |
sanctions_screen_identifier | Look up an IMO number, SWIFT/BIC code, wallet address, or passport or national ID number exactly across every loaded watchlist |
sanctions_get_designation | Fetch the full record for one designation by source list and entry ID or published reference number |
sanctions_resolve_entity | Resolve a company name, optionally within a jurisdiction, to ranked GLEIF LEI candidates |
sanctions_get_entity | Fetch the GLEIF Level 1 record for an LEI, with a sanctions cross-reference of its names, LEI, and registration number |
sanctions_trace_ownership | Trace an LEI's parents and children, optionally cross-referencing every entity in the graph |
sanctions_list_sources | List the loaded sources with record counts, licenses, and mirror freshness |
Resources
| Resource | Description |
|---|---|
sanctions://designation/{source}/{entryId} | One sanctions designation by source and entry ID or published reference number |
sanctions://entity/{lei} | One GLEIF Level 1 entity by LEI |
sanctions://sources | Loaded sources with counts, plus each mirror's readiness and as-of timestamp |
skill://sanctions-screening-guide/SKILL.md | Consumer guide for identity confirmation, ownership continuation, and screening uncertainty |
The three sanctions:// data resources are also reachable through the tools, for clients that don't surface resources. The guide is also published in the plugin, npm package, and bundle under skills/sanctions-screening-guide/.
Prompts
| Prompt | Description |
|---|---|
sanctions_vet_counterparty | Run a counterparty due-diligence pass: screen, resolve, trace ownership, summarize |
Capability reference
sanctions_screen_name tool
namein any script (at most 64 words and 1,024 characters) plus optionalsources,entityType, andminScorefilters;matchModeisstrict(default) orfuzzy. Strict runs a fuzzy pass over each selected list it finds nothing on: a full pass when it finds nothing on any list, otherwise one that adds, after the strict hits, only candidates covering every word of the name other than legal forms, articles and other function words, and the jurisdiction codesuk,usa,uae, andrf.fuzzySourcesnames the lists a fuzzy pass searched; up to 100 hits per page (limit, default 25) withoffset- Hits carry
source,sourceEntryId, the list's publishedreferenceNumberwhere it has one,matchedNamewith itsmatchedNameType, andmatchType(exact/strong/approximate); approximate hits add the raw Jaro-Winklerscore(0–1) andqueryTokenCoverage - Approximate screening compares native forms and deterministic Russian-alphabet romanizations in both directions, always keeping the name and alias type exactly as published. Derived equality remains approximate; it is neither an official alias nor an identity determination. Native candidates take priority within each list's independent 2,000-name fuzzy pool; romanized forms and first-four-code-point deletion signatures supplement remaining capacity, so typo recall is bounded
sourcesnames every selected list a hit's party is on. OFAC publishes a party on the SDN List and a non-SDN list under one entry ID in both of its files; with both OFAC lists selected that party is one hit, counted once, naming both even when the screen matched one record. Its other fields, the designation date included, come from thesourcerecord:ofac_sdn, unless the Consolidated record matched alone or bettertotalAvailable,hasMore, andnextOffsetpage the rest, and paging reaches every match counted;totalAvailableBasismarks the countexact(strict matched on every selected list) or alower_bound(a fuzzy pass ran), and anoticeasks for a more distinctive word when a list's fuzzy candidate pool reached its budget
sanctions_screen_identifier tool
value(an identifier as you hold it) plus optionaltype(anydefault,imo,swift_bic,digital_currency_address,passport,national_id) andsources- Exact match after normalization, never fuzzy and never scored: spacing, letter case, and
-./are ignored; an IMO number matches with or without itsIMOprefix; a SWIFT/BIC compares on its first eight characters, so a branch BIC11 matches its institution's BIC8; a wallet address folds case only for hex (0x…), bech32 (bc1…,ltc1…,bnb1…), and cashaddr encodings, while base58 addresses compare exactly typematches on each list's published label:imois OFACVessel Registration Identification, UKIMO Number, and EUIMO (vessel identification);swift_bicisSWIFT/BICandSWIFT BIC;digital_currency_addressis OFACDigital Currency Address - <code>;passportandnational_idcover the lists' passport and national-ID labels.anyalso reaches every label with no category (MMSI, call signs, tail numbers, tax and registration numbers, email, websites)- One hit per designation, ordered by list then entry ID, each with the
matchedIdentifiersthat matched as published and itssources, an OFAC party on both OFAC lists being one hit naming both; not paged. An identifier a list prints only in free-text remarks, or bundled with other numbers in one field, does not match
sanctions_get_designation tool
source(one of the eleven codes above) andentryId: thesourceEntryIdfrom a screening hit, or the reference number the list publishes (UNQDe.004, EUEU.27.28, UK OFSI Group ID14196). Matched trimmed and case-insensitive, entry ID first; a reference number two designations share fails asreference_ambiguous, naming both. CSL records retain the provider's opaqueid; missing entity type remainsunknown- Returns
referenceNumberbesidesourceEntryIdwhere the list publishes one; OFAC publishes none, its entry ID being its published number - All published aliases, identifiers, addresses, dates and places of birth, nationalities, descriptive features, program, legal basis, and designation date; a field the source omitted is absent, never filled in
- Identifiers cover identity documents plus the SWIFT/BIC codes, digital-currency addresses, vessel call signs, aircraft tail and serial numbers, phone numbers, email addresses, and websites a list publishes, each typed with the list's own label and its value verbatim
- Dates of birth are ISO 8601 at the precision the source published (
1952-10-07,1946-08,1938, or an interval such as1955/1957), withcirca: truewhere the source marks one approximate;designationDateis the source's own designation date asYYYY-MM-DD - An OFAC party is dated from its entries on the lists its file publishes: the SDN List for
ofac_sdn, the non-SDN lists forofac_consolidated, earliest first. A party on both files can carry a different date on each legalBasisis the instrument the list cites, verbatim: OFAC's legal-basis references (Executive Order 14024 (Russia)), the EU regulation title (2024/1488 (OJ L27052024)), or the UK regime's regulations (The Russia (Sanctions) (EU Exit) Regulations 2019), several joined with;. The UN list publishes nonefeaturescarries what a list publishes to describe the party rather than identify it, each typed with the list's own label, in published order: OFAC's vessel flag, type, owner, and tonnage, aircraft model, operator, and manufacture date, title, gender, and sanctions notes (Secondary sanctions risk:See Section 11 of Executive Order 14024.), and the UK list's ship details (CurrentBelievedFlagOfShip,TypeOfShip,YearBuilt, …). A date is ISO 8601 at published precision, withcirca: truewhere OFAC marks it approximate. The EU and UN lists contribute none, and no screening tool matches a feature value. CSL features also retain published restrictions, order flags, end dates, country, notices, and provenance URLs without legal interpretation.
sanctions_resolve_entity tool
namein any script (same bound assanctions_screen_name) plus optionaljurisdiction,status, andminScore;matchModeisstrict(default), which falls back to a fuzzy pass only when strict finds nothing, orfuzzy; up to 50 candidates per page (limit, default 10) withoffsetjurisdictionis a country code, which matches the country and every subdivision under it (USmatchesUS-DEandUS-CA), or an ISO 3166-2 subdivision code (US-DE), matched exactly; case-insensitivestatus:issued(default) matchesISSUED,lapsedmatches exactlyLAPSED, andanyapplies no filter — the only way to reachRETIRED,DUPLICATE,ANNULLED,PENDING_TRANSFER,PENDING_ARCHIVAL, orMERGEDrecords, each candidate'sstatusnaming its state- Searches every name GLEIF publishes: the legal name, previous legal names, trading names, alternative-language legal names, and ASCII transliterations of a legal name in another script
- Candidates carry
lei,legalName, thematchedNameand itsmatchedNameType(LEGAL_NAME,PREVIOUS_LEGAL_NAME,TRADING_OR_OPERATING_NAME, …, orUNKNOWNfor a name stored without a type), andmatchType, withscoreandqueryTokenCoverageon approximate matches; one candidate per LEI, paged by the sametotalAvailable/totalAvailableBasis/hasMore/nextOffsetfields
sanctions_get_entity tool
- One 20-character
lei; returns the legal name,otherNames, legal and headquarters addresses,status,jurisdiction, registration authority, andlastUpdate alternateNameslists every other and transliterated name with its GLEIF type; a name the mirror stored before types were kept reads asUNKNOWNsanctionsHitscross-references the entity against every watchlist: the legal name and everyalternateNamesentry screened strict (exact, then all tokens present, never fuzzy), and the LEI andregistrationAuthorityEntityIdlooked up exactly as non-document identifiers (never passports, national IDs, IMO numbers, SWIFT/BIC codes, or wallets). The registration number matches only an identifier published for the country of the entity'sjurisdiction, and is skipped when the entity publishes no jurisdiction or a not-available placeholder (N/A,n.a.) in place of a number- One hit per designation, an OFAC party on both OFAC lists being one hit whose
sourcesnames both, exact name and identifier matches first, then strong name matches, capped at 25 after the merge.matchedOnlists every input that produced a hit (legal_name,other_namewith its GLEIF type,lei,registration_number); an identifier match addsmatchedIdentifiersas the list publishes them, and a hit no name produced carries nomatchedNameormatchType. No hit carries ascore: only an approximate (fuzzy) match has one, and every screen here is strict screeningStatus(screened/not_ready) says whether the cross-reference ran;sanctionsScreencounts distinct designations across every input (totalAvailable), flags a capped list (hasMore), and lists inscreenedInputswhat was screened beyond the always-screened legal name and LEI
sanctions_trace_ownership tool
- Root
lei,direction(parents/children/both, defaultboth), anddepth1–5 (default 3);screenNodes: truecross-references every node assanctions_get_entitydoes (its names strict, its LEI and country-matched registration number as identifiers), up to 10 merged hits per node; a node with no Level 1 record has no name to screen and is looked up by its LEI alone.bothis a parents walk plus a children walk from the root, each todepth; it never reaches siblings or co-parents limitdefaults to 50 and clamps above 100 records acrossnodes, thenedges, thenboundaries. DrainnextCursorwhilehasMoreis true;totalAvailablecounts those segment records exactly. Edges reference nodes in the assembled segment across pages. Only returned nodes are hydrated and screened- Pages append intact records until the count cap or a soft 64-KiB serialized text-and-structured target;
byteTargetExceededdiscloses an oversized final payload. Cursors bind the effective query and topology fingerprint; invalid or stale cursors require restarting the segment. Entity and screening values remain current between pages, without an immutable snapshot roleanddepthretain segment placement; ultimate-parent shortcuts are unwalked leaves flaggedreachedVia: "ultimate". After draining a segment, re-root each boundary with its supplied direction and depth; deduplicate queued(lei, direction)roots and edge identities because segments overlap. Depth restarts per segment; a missing Level 1 root is accepted only if Level 2 references ittruncationReasonsdistinguishes remaining pages, depth limits, and ultimate leaves.completerequires no remaining page or omitted branch and no missing Level 1 record anywhere in the segment (missingEntityCount);missingEntityLeisis page-local. This covers loaded accounting-consolidation relationships only, never beneficial ownership- Each node whose parents the walk read carries
parentStatus.direct/.ultimate:relationship,exception(a GLEIF reporting exception, with every reason, such asNATURAL_PERSONS),none, orunknown.unknownmeans exception data is not loaded, whichreportingExceptionsLoaded: falsestates. screeningStatus(screened/not_requested/not_ready),screenedNodeCount, andflaggedNodeCount; each screened node carries its ownsanctionsScreen(hasMore,screenedInputs) and hits withmatchedOnandmatchedIdentifiers, in thesanctions_get_entityshape
sanctions_list_sources tool
- No input; one row per sanctions list plus
gleif, each withrecordCount,url(the configured source URL), andlicense; thegleifrow'srecordCountis Level 1 entities and itsrelationshipCountLevel 2 ownership relationships sanctionsReady/sanctionsAsOfandleiReady/leiAsOfreport whether each mirror has synced and when;sanctionsAsOfis the last successful local sync in which every sanctions list refreshed, never upstream publication freshness. Not gated on readiness, so it reports an empty mirror instead of failingreportingExceptionsLoaded, and on thegleifrow areportingExceptionCountonce the reporting exceptions are loadedalternateNamesIndexed: whether GLEIF's trading, previous, alternative-language, and transliterated names are indexed; untilmirror:initbuilds the index,sanctions_resolve_entitysearches legal names only
sanctions://designation/{source}/{entryId} resource
sourceis one of the eleven list codes,entryIdthe list's own ID or its published reference number, resolved assanctions_get_designationresolves it and decoded once when percent-encoded; returns thesanctions_get_designationpayload asapplication/json- Cached for an hour, scoped
private
sanctions://entity/{lei} resource
- The
sanctions_get_entityLevel 1 payload for onelei, without the sanctions cross-reference, which is tool-only - Cached for an hour, scoped
private
sanctions://sources resource
- The
sanctions_list_sourcespayload, unchanged ttlMs: 0: never cached, because readiness and the as-of timestamps are the payload
sanctions_vet_counterparty prompt
- Arguments:
namerequired;jurisdictionoptional (a country code, which includes its subdivisions, or an ISO 3166-2 subdivision code) - Returns one user message that screens the name (and any identifier the caller holds, with
sanctions_screen_identifier), resolves it to an LEI, traces ownership withscreenNodes: true, pulls each hit's designation record, and asks for a summary that treats every match as a candidate to verify and says, frommatchedOn, which of the entity's names or identifiers produced it. It names GLEIF parents as accounting-consolidation parents, not beneficial owners, and reports a reporting exception's reasons rather than reading it as "no parent"
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Sanctions-screening-specific:
- One screen covers the eleven sanctions and export-control lists above; the matching list shows up as per-hit provenance
- Offline and keyless, with no per-request upstream rate limit: all lists are normalized into local SQLite + FTS5 mirrors via the framework
MirrorService - Per-alias name index, so a query matches any of an entity's names in one FTS scan, and a per-identifier index for exact lookup by IMO number, SWIFT/BIC, wallet address, or document number
- Strict-then-fuzzy matching: exact-normalized, then all tokens present (FTS5), then Jaro-Winkler + Double-Metaphone over a 2,000-candidate pool per list, so adding a list never pushes another list's candidates out, with every admitted candidate reachable by paging
- GLEIF Level 1 (who is who) and Level 2 (who owns whom) for entity resolution and ownership tracing
Agent-friendly output:
- Real signal with provenance: approximate hits carry the raw Jaro-Winkler
scoreand a literalqueryTokenCoveragecount, never a blended confidence; every hit names its list, program, designation date, and the name that matched, typedprimary/aka/fka/low-quality-aka - Decision-support
caveatin the output ofsanctions_screen_name,sanctions_screen_identifier,sanctions_get_designation,sanctions_get_entity, andsanctions_trace_ownership - Disclosed gaps:
totalAvailableBasis,screeningStatus,complete/truncated/missingEntityLeis, and each node'sparentStatussay what a response did not cover - Typed errors: every data tool and
sanctions://resource except the sources listing fails asmirror_not_ready(retryable) until its mirror is loaded; unknown IDs fail asdesignation_not_foundorlei_not_found, an LEI the mirror lacks whose ISO 17442 check digits fail asinvalid_lei_checksum(a mistyped character), and a reference number two designations share asreference_ambiguous; a name with no letter or digit fails asname_not_searchable, one past the length bound asname_too_long, and an identifier with nothing left once spacing and separators are removed asidentifier_not_searchable
Getting started
Public Hosted Instance
A public instance is available at https://sanctions-screening.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"sanctions-screening-mcp-server": {
"type": "streamable-http",
"url": "https://sanctions-screening.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file. The mirror is not bundled, and the npm package cannot build it: run bun run mirror:init from a clone (Installation) or inside the Docker image, then point SANCTIONS_MIRROR_PATH at the result (see Mirror lifecycle).
{
"mcpServers": {
"sanctions-screening-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/sanctions-screening-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"sanctions-screening-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/sanctions-screening-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- Disk for the local mirror; GLEIF Level 1 takes most of it. No source needs an API key.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/sanctions-screening-mcp-server.git
- Navigate into the directory:
cd sanctions-screening-mcp-server
- Install dependencies:
bun install
- Configure environment:
cp .env.example .env
# edit .env if you need to override defaults (all optional)
- Populate the mirror:
bun run mirror:init
Configuration
Every source is keyless, so nothing here is required.
| Variable | Description | Default |
|---|---|---|
SANCTIONS_MIRROR_PATH | Filesystem path for the SQLite mirror; use a persistent volume when hosted. | ./data/sanctions.db |
SANCTIONS_REFRESH_CRON | Cron for the scheduled refresh (HTTP transport only). A supervised child refreshes the sanctions lists and their indexes, then applies GLEIF deltas under one fresh 4-hour bound. A gap that needs mirror:init is logged; the schedule never loads golden copies. | 0 4 * * * |
SANCTIONS_REFRESH_SKIP_GLEIF | Set to 1 to skip the GLEIF leg of the scheduled refresh and of mirror:refresh. | unset |
SANCTIONS_FUZZY_MIN_SCORE | Jaro-Winkler floor for fuzzy matches when minScore is omitted. | 0.85 |
OFAC_SDN_URL | OFAC SDN advanced-XML URL. | official OFAC URL |
OFAC_CONSOLIDATED_URL | OFAC Consolidated advanced-XML URL. | official OFAC URL |
EU_FSF_URL | EU consolidated XML URL. Its token query value is a static public path component, not a credential. | official EU URL |
UK_SANCTIONS_URL | UK Sanctions List (UKSL) XML URL. | official FCDO URL |
UN_SC_URL | UN Security Council consolidated XML URL. | official UN URL |
CSL_URL | Mirror-download override for the shared BIS/State CSL bulk JSON; no search API key. | https://data.trade.gov/downloadable_consolidated_screening_list/v1/consolidated.json |
GLEIF_GOLDEN_COPY_BASE_URL | GLEIF golden-copy and delta download API. | https://goldencopy.gleif.org |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. createApp() declares stateless; setting the variable overrides it. | stateless |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:http -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security, changelog sync bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Mirror lifecycle
Upgrading from 0.5.1 or earlier: run bun run mirror:refresh to load the six BIS/State CSL lists; optionally set SANCTIONS_REFRESH_SKIP_GLEIF=1. Existing readiness does not prove those lists are populated. sanctions_list_sources reports configured lists and stored counts, with refresh guidance for zero-row CSL sources.
The mirror loads out-of-band, never on the request path: bun run mirror:init does the full load once, bun run mirror:refresh keeps it current (HTTP deployments also refresh on SANCTIONS_REFRESH_CRON), and bun run mirror:verify reports readiness and counts. When upgrading from an earlier release, run mirror:refresh once (from 0.3.0 or earlier, mirror:init). Failure handling, upgrade and rollback behavior, and disk sizing are in docs/mirror.md.
Docker
docker build -t sanctions-screening-mcp-server .
docker run --rm -p 3010:3010 -v sanctions-data:/usr/src/app/data sanctions-screening-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/sanctions-screening-mcp-server. Mount a volume at /usr/src/app/data so the mirror survives restarts, and populate it with bun run mirror:init via docker exec. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point: registers the surface, inits the screening service, schedules the HTTP refresh. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts) and the shared screening caveat. |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts). |
src/services/screening | Screening service: mirrors, normalized schema, OFAC/EU/UK/UN/CSL/GLEIF ingesters, matching engine. |
skills/sanctions-screening-guide/ | Consumer workflow guide, published through MCP, plugins, npm, and the bundle. |
scripts/mirror-*.ts | Mirror lifecycle CLI: init, refresh, verify, seed. |
tests/ | Tool, resource, prompt, service, integration, fuzz, and smoke tests. |
Development guide
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging; the mirror is reached throughgetScreeningService(), notctx.state - Register new tools and resources via the barrels in
src/mcp-server/*/definitions/index.ts - Wrap external sources: validate raw → normalize to the common schema → return the output schema; never fabricate fields a source omits, and never synthesize a confidence score
Attribution
This server redistributes open data from these sources, cited per their terms:
| Source | Publisher | License |
|---|---|---|
| OFAC SDN and Consolidated lists | US Department of the Treasury, Office of Foreign Assets Control | US Government public domain |
| BIS Entity, Denied Persons, Unverified, Military End User; State ITAR Debarred and Nonproliferation Sanctions | US Departments of Commerce and State, via ITA CSL | US Government public domain; ITA terms |
| EU Consolidated Financial Sanctions List | European Commission / EEAS | Freely redistributable |
| UK Sanctions List (UKSL) | UK Foreign, Commonwealth & Development Office | Open Government Licence v3.0 (attribution required) |
| UN Security Council Consolidated List | United Nations Security Council | Freely redistributable |
| GLEIF LEI data (Level 1 + Level 2) | Global Legal Entity Identifier Foundation | CC0 1.0 Universal |
UKSL has been the single authoritative UK source since the OFSI Consolidated List closed on 28 January 2026.
This product uses the International Trade Administration’s Data API but is not endorsed or certified by the International Trade Administration.
Verify CSL candidates and their published restrictions against the official agency lists and Federal Register publications. A published end date or order flag is retained as source data, never interpreted as clearance or a legal-status determination. Mirror timestamps report successful local synchronization, not the publication time or freshness of each upstream list.
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.