OFAC SDN + UN Consolidated - Vessel Sanctions Delta API avatar

OFAC SDN + UN Consolidated - Vessel Sanctions Delta API

Pricing

from $0.50 / 1,000 sanctioned vessel records

Go to Apify Store
OFAC SDN + UN Consolidated - Vessel Sanctions Delta API

OFAC SDN + UN Consolidated - Vessel Sanctions Delta API

Automated compliance screening: cross-references OFAC SDN vessel sanctions against the UN Consolidated List by IMO number. Delta-engine flags new designations, program changes, and delistings as structured JSON, on any schedule you set, with no API keys required.

Pricing

from $0.50 / 1,000 sanctioned vessel records

Rating

0.0

(0)

Developer

Stefano Seggio

Stefano Seggio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Categories

Share

Maritime Sanctions Watchdog - OFAC & UN Vessel Screening (Global Trade Compliance)

Built for Apify Pay-Per-Event pricing TypeScript ./LICENSE

Run on Apify

This Actor monitors the US Treasury OFAC SDN vessel sanctions list cross-referenced against the UN Security Council Consolidated List (Global — OFAC / UN jurisdiction, by IMO number), and runs whenever you trigger it or schedule it on your own Apify Scheduler — there is no fixed operator-side cadence.

Architecture

This diagram reflects the actual real module call graph in ./src — every node names the real file that implements it, not an illustrative simplification.

flowchart TD
A[OFAC SDN.XML feed] --> C["fetchVesselRecords.ts"]
B[UN Security Council<br/>Consolidated List] --> C
C --> D["parseSdnXml.ts /<br/>unConsolidatedList.ts"]
D --> E["fingerprint.ts<br/>statusFingerprint + contentFingerprint"]
S[("state.ts<br/>Apify Key-Value Store<br/>(persisted delta state)")] -.load prior fingerprints.-> E
E --> F{"delta.ts<br/>compare vs prior state"}
F -->|first sighting| G1["SANCTION"]
F -->|program changed| G2["STATUS_CHANGE"]
F -->|other field changed| G3["UPDATED"]
F -->|identical| G4["SNAPSHOT_NO_DIFF<br/>(never billed)"]
F -->|missing from feed| G5["DELISTED"]
G1 --> H["umsNormalizer.ts"]
G2 --> H
G3 --> H
G5 --> H
H --> I["Actor.pushData()<br/>Apify dataset<br/>PPE billed: $0.0005/event"]
F -.saveState.-> S

Executive Value Proposition

The US Treasury's OFAC Specially Designated Nationals (SDN) list runs to roughly 19,000 entries in a single raw XML feed, of which about 1,540 are vessel-type designations - and the UN Security Council Consolidated Sanctions List carries no dedicated vessel record at all, only free-text IMO mentions buried in individual/entity remarks. Manually pulling both feeds, filtering to vessels, and cross-checking IMO numbers by eye is slow, easy to get wrong, and impractical to repeat on a schedule. This Actor automates that entire lookup - one run turns two raw government/UN feeds into one normalized, IMO-cross-referenced vessel dataset, and recurring runs add real change detection (new designations, sanctions-program changes, and delistings) instead of a manual re-diff every time.

Use Cases

  • Vendor and counterparty KYC screening. Before onboarding a shipping counterparty, charterer, or logistics vendor, screen the vessels they operate against the current OFAC vessel-type SDN list and see immediately whether any carry independent UN corroboration via IMO number.
  • Shipping and logistics risk checks. Freight forwarders, marine insurers, and charterers can use vesselNameContains to check one vessel of interest before booking cargo or binding coverage, or run programFilter (e.g. ["IRAN"], ["RUSSIA-EO14024"]) to watch an entire sanctions program relevant to a trade lane.
  • Export-compliance due diligence monitoring. Compliance teams running recurring screening programs can schedule this Actor with onlyNew enabled to get only what changed since the last run - new sanctions, program additions/removals, and delistings - instead of re-reviewing the full list every cycle.

Cost & BYOK Disclosure

Pricing (Pay-Per-Event)

This Actor is monetized with Apify's Pay-Per-Event model: pricing is per discrete event, not per platform-compute-second. Two event types are configured, verified live against this Actor's own Apify API pricing resource:

Event nameEvent titleWhat triggers itPrice
apify-actor-startActor StartOnce per run, when the Actor starts - a platform-standard PPE event, charged at one unit per GB of configured memory (minimum one unit). This Actor runs at 2048 MB, so 2 units fire per run.$0.00005 per unit ($0.0001 per run at this Actor's memory tier)
resultSanctioned Vessel RecordEvery dataset item pushed - a SANCTION, STATUS_CHANGE, UPDATED, or DELISTED record$0.0005 per event

Both source feeds (OFAC's SDN.XML and the UN Consolidated List) are plain unauthenticated file downloads with no per-record request cost, so there's no proxy or per-page fetch overhead passed through in the price beyond the fixed per-run apify-actor-start charge above.

How unchanged-record suppression actually works

Each vessel gets two SHA-1 fingerprints, computed by ./src/fingerprint.ts: a statusFingerprint over its sorted sanctions-program list, and a contentFingerprint over its other mutable fields (name, remarks, identifiers, flag, tonnage, owner). Comparing these against the previous run's persisted fingerprint classifies every vessel as SANCTION (first sighting), STATUS_CHANGE (program fingerprint changed), UPDATED (content fingerprint changed), or SNAPSHOT_NO_DIFF (identical to last time).

What actually gets billed depends on the onlyNew input:

  • onlyNew: true (recommended for recurring monitoring): a SNAPSHOT_NO_DIFF vessel is skipped before it is ever pushed to the dataset - no result event fires, so it is genuinely $0.00, not billed, and not a refund applied after the fact.
  • onlyNew: false (the default, for a full point-in-time census): every vessel currently on the OFAC vessel-type list is pushed, including unchanged ones marked SNAPSHOT_NO_DIFF - each still fires one result event at $0.0005, because you explicitly asked for the complete current list rather than only what changed.

A previously-seen vessel absent from the current OFAC feed is always reported DELISTED (and billed) regardless of onlyNew, unless the run was truncated by maxItems, in which case DELISTED detection is skipped for that run entirely rather than guessed at.

BYOK (Bring Your Own Key)

This Actor requires no third-party API key. Both source feeds - OFAC's SDN.XML and the UN Security Council Consolidated List - are open, unauthenticated government/UN downloads with no login, API key, or paid tier of any kind.

Quickstart

The Actor's real slug is stefano_seggio/actor-19-maritime-sanctions-monitor (Actor ID dR68wHyuOLS2WEhmo, works interchangeably in all three clients below).

cURL (instant, synchronous)

Runs synchronously and returns the resulting dataset items directly in the response - no polling needed. Get your token from console.apify.com/settings/integrations.

curl -X POST "https://api.apify.com/v2/acts/dR68wHyuOLS2WEhmo/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"maxItems": 50,
"onlyNew": true
}'

Python (apify-client)

import os
from apify_client import ApifyClient
client = ApifyClient(token=os.environ["APIFY_TOKEN"])
run_input = {
"maxItems": 100,
"onlyNew": True,
"enrichWithUnConsolidatedList": True,
"programFilter": ["IRAN", "RUSSIA-EO14024"],
}
run = client.actor("stefano_seggio/actor-19-maritime-sanctions-monitor").call(run_input=run_input)
items = client.dataset(run["defaultDatasetId"]).list_items().items
for item in items:
imo = item.get("imoNumber") or "n/a"
print(f"{item['vesselName']} (IMO {imo}) - {item['event_type']}")

A full runnable copy lives at ./examples/run_actor.py.

Node.js (apify-client)

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('stefano_seggio/actor-19-maritime-sanctions-monitor').call({
maxItems: 100,
onlyNew: true,
enrichWithUnConsolidatedList: true,
programFilter: ['IRAN', 'RUSSIA-EO14024'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
console.log(`${item.vesselName} (IMO ${item.imoNumber ?? 'n/a'}) - ${item.event_type}`);
}

A full runnable copy (CommonJS) lives at ./examples/run-actor.js.

Apify CLI

apify call actor-19-maritime-sanctions-monitor --input '{
"maxItems": 100,
"onlyNew": true,
"enrichWithUnConsolidatedList": true,
"programFilter": ["IRAN", "RUSSIA-EO14024"]
}'

Use this from Claude Desktop, Cursor, or Windsurf (via MCP)

This Actor is also reachable as a tool through Apify's own hosted @apify/actors-mcp-server at https://mcp.apify.com, scoped to just this one Actor via a ?tools=stefano_seggio/actor-19-maritime-sanctions-monitor query string - it is not a separate "Delta Registry MCP server," and each config below connects an MCP client to this single Actor, not the wider fleet. Get a token from Apify Console → Settings → Integrations first.

Claude Desktop

Edit %APPDATA%\Claude\claude_desktop_config.json on Windows (~/Library/Application Support/Claude/claude_desktop_config.json on macOS). Claude Desktop connects via the mcp-remote stdio bridge, not a direct URL:

{
"mcpServers": {
"delta-registry-actor-19-maritime-sanctions-monitor": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.apify.com/?tools=stefano_seggio/actor-19-maritime-sanctions-monitor",
"--header",
"Authorization: Bearer ${APIFY_TOKEN}"
]
}
}
}

mcp-remote does not expand shell environment variables inside the JSON string - paste your real token literally in place of ${APIFY_TOKEN}, and keep this file out of version control.

Cursor

Edit .cursor/mcp.json (project-scoped) or ~/.cursor/mcp.json (global). Cursor uses native HTTP transport:

{
"mcpServers": {
"delta-registry-actor-19-maritime-sanctions-monitor": {
"url": "https://mcp.apify.com/?tools=stefano_seggio/actor-19-maritime-sanctions-monitor",
"headers": {
"Authorization": "Bearer ${APIFY_TOKEN}"
}
}
}
}

Windsurf

Edit ~/.codeium/windsurf/mcp_config.json. Windsurf uses serverUrl, not url:

{
"mcpServers": {
"delta-registry-actor-19-maritime-sanctions-monitor": {
"serverUrl": "https://mcp.apify.com/?tools=stefano_seggio/actor-19-maritime-sanctions-monitor",
"headers": {
"Authorization": "Bearer ${env:APIFY_TOKEN}"
}
}
}
}

Windsurf's ${env:...} syntax genuinely resolves from your environment at runtime, unlike mcp-remote above.

Want every Delta Registry Actor (all 28) reachable from one closed-scope MCP config instead of connecting to each Actor individually? See delta-registry-website/MCP_INTEGRATION.md.

Input & Output Schema

Input

FieldTypeDefaultDescription
maxItemsinteger250Hard cap on how many vessel-type SDN records to return this run, taken in the order OFAC lists them in the source feed.
onlyNewbooleanfalseDelta mode - persists a SHA-1 content fingerprint per vessel between runs and returns only records that are new or changed since the last run. Vessels no longer present in the current OFAC feed are always reported as DELISTED regardless of this setting (unless the run was truncated by maxItems). Recommended for recurring monitoring.
enrichWithUnConsolidatedListbooleantrueCross-references each vessel's IMO number against the UN Security Council Consolidated Sanctions List. Disable for a faster OFAC-only run.
programFilterarray of strings-Optional: only return vessels whose Program list contains at least one of these OFAC program codes (case-insensitive substring match, e.g. "IRAN", "DPRK2", "RUSSIA-EO14024"). Leave empty to return vessels from all programs.
vesselNameContainsstring-Optional case-insensitive substring filter on the vessel's listed name, applied before maxItems. Useful for a narrow monitoring run against one vessel of interest.

Full machine-readable definition: ./.actor/input_schema.json.

Sample Extracted Dataset (JSON)

One real record from this Actor's own dataset, matching ./.actor/dataset_schema.json:

{
"uid": "15036",
"vesselName": "ARTAVIL",
"programs": [
"IRAN"
],
"imoNumber": "9187629",
"vesselFlag": "Iran",
"crossReferencedInUnConsolidatedList": true,
"record_id": "OFAC-SDN-15036",
"event_type": "SANCTION",
"scraped_at": "2026-09-08T14:00:00.000Z",
"is_new": true,
"source_url": "https://sanctionssearch.ofac.treas.gov/Details.aspx?id=15036"
}

Each dataset item also carries the vessel's other native OFAC fields (MMSI, other identifiers, call sign, vessel type, tonnage, owner, remarks, and AKA names) - omitted above only because they were empty for this particular vessel.

Output field reference

FieldTypeDescription
uidstringOFAC's own stable internal identifier for this SDN entry (the <uid> element / CSV ent_num column). Stable across list republications.
vesselNamestringThe vessel's listed name.
programsstring[]OFAC program code(s) this designation falls under, e.g. IRAN, DPRK2.
imoNumberstring | null7-digit IMO ship identification number. Null when OFAC has not recorded one for this vessel - a real, documented gap, not every SDN vessel entry carries an IMO.
vesselFlagstring | nullThe vessel's flag state as listed by OFAC.
crossReferencedInUnConsolidatedListbooleantrue when this vessel's IMO number was also found in the UN Security Council Consolidated Sanctions List. Always false when enrichWithUnConsolidatedList was disabled, or when the vessel has no imoNumber to match on.
record_idstringOFAC-SDN-<uid> - stable across runs.
event_typestringOne of SANCTION (first sighting), STATUS_CHANGE (sanctions program(s) changed), UPDATED (another tracked field changed), SNAPSHOT_NO_DIFF (identical to last time - only delivered when onlyNew is off), or DELISTED (no longer in the current OFAC feed).
scraped_atstringISO-8601 timestamp of this extraction.
is_newbooleantrue if this vessel was not seen in a prior run.
source_urlstringPer-vessel permalink on OFAC's own public Sanctions List Search tool.

The full field list (including unConsolidatedListMatches, otherIds, akaNames, and the fleet-standard normalized envelope) is in ./.actor/dataset_schema.json.

Environment Variables

None required. This Actor takes zero third-party API keys or BYOK secrets - both source feeds (OFAC's SDN.XML and the UN Security Council Consolidated List) are open, unauthenticated downloads. The only environment variables present at runtime are the standard ones Apify's platform and SDK inject automatically into every Actor run (APIFY_TOKEN, APIFY_ACTOR_ID, APIFY_DEFAULT_KEY_VALUE_STORE_ID, etc.) - these are managed by the apify npm package and the Apify platform itself, never set manually by you or read directly by this Actor's own code.

Operational Telemetry Limits

Real, measured values from this Actor's own production run history and current live platform configuration - not modeled or estimated:

MetricValueBasis
Configured timeout (defaultRunOptions.timeoutSecs)300sSet from real telemetry (see below), not the Apify platform default of 3600s.
Configured memory (defaultRunOptions.memoryMbytes)2048 MB~3.5x the real observed peak, rounded to the nearest Apify memory tier.
Real max observed runtime6.8s (n=3 production runs)Pulled from each run's own stats.runTimeSecs via the Apify API.
Real avg observed runtime6.5s (n=3)Same source.
Real peak memory used586 MB (n=3)From each run's own stats.memMaxBytes.
Effective safety margin~44x300s timeout vs. 6.8s worst observed run.
ConcurrencyNot configurable at the platform levelApify's defaultRunOptions has no concurrency field - confirmed by inspecting the full Actor API resource. This Actor makes no internal concurrent-fetch parameter either (both source feeds are fetched sequentially, not paginated/fanned out).

These figures were established via a fleet-wide real-telemetry audit (all 28 actors in the wider Delta Registry fleet), not set once at Actor creation and left unexamined - see this repository's own run history via apify api get "acts/stefano_seggio~actor-19-maritime-sanctions-monitor/runs" to reproduce.

Reliability

  • Retrying HTTP layer. Both source feeds are fetched with exponential-backoff retry (up to 4 retries, doubling from a 1-second base delay), with HTTP 429/503 explicitly treated as retryable alongside network failures and other non-2xx statuses. Both OFAC and UN endpoints redirect (302) to a pre-signed storage URL before returning the actual file; the client follows redirects by default.
  • A delta-state failure never blocks a fresh extraction. Loading the persisted cross-run fingerprint state is a real network call to the Apify platform's key-value store and is treated as fallible: if it fails, the run logs the error and falls back to a cold-start (empty) delta state rather than crashing before a single record is fetched.
  • Extraction success is decoupled from state-persistence success. If saving the updated delta state fails after a run's records were already pushed, that failure is logged on its own and does not retroactively mark an already-successful extraction as failed, and does not push a spurious error record on top of real data already in the dataset.
  • Process-level safety net. Top-level unhandledRejection/uncaughtException handlers guarantee any failure outside the main try/catch blocks is written to the captured log stream before the process exits, rather than exiting silently.
  • Truncation-aware delisting logic. The OFAC feed is always fetched and parsed in full - never paginated - so a previously-seen vessel that's genuinely absent is a trustworthy DELISTED signal, not a guess. If maxItems caps delivery before the full feed is walked, DELISTED detection is skipped for that run (and logged) and the vessel-fingerprint state is merged rather than replaced, so vessels beyond the cutoff are never wrongly reported as delisted next run.
  • Scoped UN cross-referencing. IMO numbers are matched against the UN Consolidated List per-record (within each entity/individual's own remarks field), not via a flat whole-document scan, avoiding misattributing an IMO number to the wrong neighboring entity.
  • Source integrity. Paris MoU/EMSA THETIS, Tokyo MoU/APCIS, and IMO GISIS were live-checked as candidate sources and excluded because they sit behind a login wall, a CAPTCHA-protected search form, or a robots.txt-restricted, registration-gated module, respectively. Only the two genuinely open, unauthenticated OFAC and UN feeds are used - no CAPTCHA-solving, login-wall bypass, or session spoofing anywhere in this Actor.

Contributing & Local Setup

This repository ships the Actor's real, buildable TypeScript source (src/, package.json, test/) - local development against real logic is fully possible here:

git clone https://github.com/stefanoseggio/actor-19-maritime-sanctions-monitor.git
cd actor-19-maritime-sanctions-monitor
npm install
apify login # once per machine
apify run # full local Actor run via the Apify CLI, reads ./storage/key_value_stores/default/INPUT.json
npm test # vitest run

No third-party credentials are required to run this Actor locally - both source feeds are open and unauthenticated.

Bugs, source-coverage requests (e.g. a new sanctions list to cross-reference), or proposed schema extensions are welcome via GitHub issues/PRs on this repository, or through the Apify Store's Issues tab on the live Actor page for non-code questions. New sources are evaluated against the same compliance doctrine used to build this Actor - no CAPTCHA-solving, no login-wall bypass.

Support & Enterprise SLA

This Actor is built and maintained by an independent developer, not a formal enterprise vendor - there is no contractual SLA. Issues, bugs, and source-coverage requests are handled through the Apify Store's Issues tab and are typically triaged within 48 hours. If you need a new data source evaluated or a schema extension, open an issue there with details and it will be reviewed against the same compliance doctrine (no CAPTCHA-solving, no login-wall bypass) used to build the rest of this Actor.


This Actor is part of Delta Registry - pay-per-event regulatory & compliance data infrastructure built and operated by Stefano Seggio. For professional inquiries or enterprise licensing, connect on LinkedIn; for the rest of the fleet, see github.com/stefanoseggio.