Global Maritime & Vessel Sanctions Monitor avatar

Global Maritime & Vessel Sanctions Monitor

Pricing

from $0.50 / 1,000 sanctioned vessel records

Go to Apify Store
Global Maritime & Vessel Sanctions Monitor

Global Maritime & Vessel Sanctions Monitor

Extracts sanctioned-vessel records from the OFAC SDN List (1,540 vessel entries), cross-referenced against the UN Consolidated List by IMO number. Paris MoU, Tokyo MoU, and IMO GISIS were checked and rejected as CAPTCHA/login-gated - see README.md.

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

15 hours ago

Last modified

Categories

Share

Global Maritime & Vessel Sanctions Monitor — Apify Store Overview

Actor ID: dR68wHyuOLS2WEhmo · Store URL: https://apify.com/stefano_seggio/actor-19-maritime-sanctions-monitor · Version: 2.0


Executive Summary & Business Use Case

The Global Maritime & Vessel Sanctions Monitor extracts every vessel-type entry from the US Treasury OFAC Specially Designated Nationals (SDN) List — the official, government-published list of individuals, entities, and vessels whose US-jurisdiction assets are blocked — pulled directly from OFAC's own live XML export (sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML, 1,540 sdnType=Vessel entries out of 19,329 total SDN entries at last live count). Each vessel's IMO number is additionally cross-referenced, when enabled, against the UN Security Council Consolidated Sanctions List (scsanctions.un.org) by matching the IMO number against the free-text designation fields of UN entity/individual records — the UN list has no dedicated vessel record type of its own, so this is an IMO-based corroboration signal, not a second structured vessel feed. Three other candidate sources named in this actor's original brief — Paris MoU/EMSA THETIS, Tokyo MoU/APCIS, and IMO GISIS — were live-checked and explicitly rejected as CAPTCHA- or login-gated (see the Technical Features section below); this actor only ever touches genuinely open, unauthenticated government file downloads.

This data is built for teams whose job is to know whether a specific vessel is currently sanctioned, not to price a transaction — OFAC's SDN List blocks a vessel's assets, it does not carry a monetary designation value, and the actor's own README says so plainly. Three concrete, real use cases follow directly from that: trade compliance and denied-party screening teams at banks, freight forwarders, and commodity trading houses screen a vessel named on a bill of lading, charter party, or letter of credit against programs/imoNumber before agreeing to finance, insure, or move a shipment on it. Marine insurance and P&I club underwriting and claims teams screen a prospective hull, cargo, or liability risk before binding cover, then run this actor on a recurring schedule with onlyNew enabled so a vessel already on cover that gets newly designated, has its sanctions program change, or is later delisted surfaces automatically rather than being caught in a periodic manual re-check. Shipping agencies, charterers, and port/terminal compliance desks use programFilter (e.g. ["IRAN"], ["DPRK2"], ["RUSSIA-EO14024"]) to run a narrow, trade-lane-specific watch — for example an Iran-sanctions-focused desk monitoring only vessels newly added to or dropped from the IRAN program, rather than the full 1,540-vessel list.

Technical Features & V2 Architecture Highlights

Two verified-open sources, two explicitly rejected ones. Both live data sources are plain, stateless, unauthenticated file downloads (a bare fetch() with default redirect-following and exponential-backoff retry on network failure and HTTP 429/503 is the entire client, src/http.ts) — no headless browser, no proxy, no CAPTCHA-solving anywhere in this actor. The rejected sources were each verified with a concrete, documented technical reason: Paris MoU / EMSA THETIS redirects into a Keycloak OpenID Connect login screen (am3.emsa.europa.eu/realms/emsa/protocol/openid-connect/auth) on every request; Tokyo MoU / APCIS's public search form carries a literal <input name="captcha"> field; IMO GISIS's robots.txt disallows the relevant paths and its public module is session/registration-gated for real search. None were force-scraped.

Named, cross-run key-value-store delta persistence. When onlyNew is enabled, the actor opens a named key-value store, actor-19-maritime-sanctions-monitor-delta-state (src/state.ts) — deliberately not Actor.getValue()/setValue(), which resolve to the store associated with the current run and would not survive between scheduled runs. This store persists, per vessel uid, a dual fingerprint pair plus enough identity (vesselName, imoNumber) to synthesize a meaningful DELISTED record later without re-fetching data that, by definition, is no longer in the feed. State is capped at 5,000 entries (oldest by lastSeenAt evicted first) and is REPLACED wholesale on a complete, untruncated run but MERGED onto prior state on a maxItems-truncated run, so vessels beyond a truncated walk's cutoff are never wrongly forgotten and re-reported as "new" later.

Dual content-fingerprint design, not a single hash. src/fingerprint.ts computes two independent SHA-1 hashes per vessel: a statusFingerprint over the vessel's sorted programs list alone (the closest thing to a "status" this domain has — a still-listed vessel can be added to or dropped from one sanctions program without being delisted outright), and a contentFingerprint over its other mutable fields (name, remarks, linked-to entity, aka names, IMO, MMSI, other IDs, call sign, type, flag, tonnage, owner). This split is what lets event_type distinguish a genuine program change from an unrelated data update.

Domain-specific event_type vocabulary — not the fleet's generic NEW_LISTING/CLOSED pattern. Per dataset_schema.json and src/delta.ts, this actor's real event types are: SANCTION (first time this vessel has been seen — kept as SANCTION, not a generic "listing," because an OFAC SDN designation genuinely is a sanction action), STATUS_CHANGE (the vessel's sanctions program(s) changed since it was last seen), UPDATED (some other tracked field changed but programs didn't), SNAPSHOT_NO_DIFF (identical to last time — only ever delivered when onlyNew is off), and DELISTED (a previously-seen vessel is absent from the current OFAC feed). There is no CLOSED event in this actor at all — a sanctions delisting and a tender closing are different real-world events, so this actor does not reuse the fleet's generic vocabulary where it doesn't fit.

DELISTED is a trustworthy signal here because the source is a complete census. The OFAC SDN.XML feed is always fetched and parsed as one complete, unpaginated file (confirmed live: 19,329 total SDN entries), so a previously-seen uid genuinely absent from a fresh fetch is a real delisting, not just "not linked from this particular run" the way a paginated or query-narrowed source would be ambiguous about. That guarantee is gated on truncatedByMaxItems being false in src/fetchVesselRecords.ts: maxItems caps how many records this run delivers, never how much of the underlying OFAC feed is read, but the walk that records "this uid was seen this run" runs inside the same loop as delivery — so once maxItems triggers a break, later entries are never visited either, and DELISTED detection is skipped for that run rather than risk a false positive (logged when it happens). DELISTED records also carry fewer fields than a normal record by design: record_id, event_type, uid, vesselName, imoNumber, scraped_at, is_new (always false), source_url, and lastConfirmedListedAt — a delisted vessel's programs, flag, owner, and other native fields are honestly left absent rather than backfilled with stale last-known values.

What onlyNew actually does, per this actor's own input_schema.json: "When enabled, persists a content fingerprint per vessel (uid) between runs in this actor's own key-value store and returns only records that are new since the last run OR whose sanctions program(s) or other tracked fields changed since they were last seen — records identical to last time are skipped. Vessels no longer present in the current OFAC feed are always reported as DELISTED regardless of this setting (when the run was not itself truncated by maxItems...). Recommended for recurring monitoring; leave off for a full extraction of the current vessel-type SDN list." Concretely, this is a disclosed v2 behavior upgrade: onlyNew now means "new or changed," delivering STATUS_CHANGE/UPDATED records too, not just first-ever sightings (per CHANGELOG.md 2.0.0) — a vessel's sanctions program changing is exactly the kind of thing a recurring compliance monitor should surface, not silently suppress just because the vessel itself was seen before.

Internal 18-field Unified Master Schema (UMS), separate from the public dataset shape. src/schemas.ts defines a UnifiedRecordSchema — a fixed 18-field contract (record_id, event_type, scraped_at, is_new, source_url, recipient_or_defendant_name, entity_identifier_native, value_native, value_currency, value_usd_normalized, effective_date_iso, publish_date_iso, category_or_type, status_or_estado, awarding_or_regulating_agency, jurisdiction, source_document_url, reference_number) used to normalize every record for the fleet's shared cross-actor entity graph. Every pushed record is validated against this UMS contract as a non-blocking correctness check (a failure is logged loudly, not silently swallowed, per main.ts) — but the UMS envelope is not what gets pushed to this actor's own Apify dataset. The dataset item is the richer, native OFAC/UN field set documented below; the UMS is an internal normalization adapter (src/umsNormalizer.ts), matching the convention used across this fleet's other actors.

UN cross-reference is free-text matching, not a structured join. Because the UN Consolidated List carries no dedicated vessel record type, crossReferencedInUnConsolidatedList is computed by regex-matching an "IMO Number: NNNNNNN" pattern inside UN entity/individual designation text (src/unConsolidatedList.ts) — a genuine, documented corroboration signal, not a fabricated structured UN vessel record.

Input Schema & JSON Configuration Example

FieldTypeDefaultDescription
maxItemsinteger (min 1)250"Hard cap on how many vessel-type SDN records to return this run, taken in the order OFAC lists them in the source feed."
onlyNewbooleanfalse"When enabled, persists a content fingerprint per vessel (uid) between runs in this actor's own key-value store and returns only records that are new since the last run OR whose sanctions program(s) or other tracked fields changed since they were last seen - records identical to last time are skipped. Vessels no longer present in the current OFAC feed are always reported as DELISTED regardless of this setting (when the run was not itself truncated by maxItems - see the DELISTED section of README.md). Recommended for recurring monitoring; leave off for a full extraction of the current vessel-type SDN list."
enrichWithUnConsolidatedListbooleantrue"When enabled (default), additionally fetches the UN Security Council Consolidated Sanctions List and flags OFAC vessel records whose IMO number is also mentioned in a UN entity's designation text. Adds one extra live fetch and a linear scan of the UN list per run; disable for a faster OFAC-only run."
programFilterarray of strings (stringList editor)(none — all programs)"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 (textfield editor)(none)"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."

A full recurring-monitor configuration, valid against input_schema.json:

{
"maxItems": 100,
"onlyNew": true,
"enrichWithUnConsolidatedList": true,
"programFilter": ["IRAN", "DPRK2"],
"vesselNameContains": "ARTAVIL"
}

A minimal one-off full extraction (all defaults):

{
"maxItems": 250,
"onlyNew": false,
"enrichWithUnConsolidatedList": true
}

Output Dataset Sample & Data Dictionary

A note on the identifier field. dataset_schema.json documents this field under the label ofacUid, but the actor's deployed extraction code (src/types.ts, src/parseSdnXml.ts) and the actor's own README output example both actually emit it as uid — that is the field name a real API response returns, and it's what the table and JSON example below use, so this documentation matches what you'll actually receive.

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 on the OFAC SDN List.
sdnTypestringAlways "Vessel" — this actor filters the SDN list to this type only.
programsarray of stringsOFAC program code(s) this designation falls under, e.g. IRAN, DPRK2.
remarksstring | nullOFAC's free-text remarks field for this entry — the source text linkedTo is extracted from.
linkedTostring | nullBest-effort extraction of a "Linked To: X" clause from remarks, e.g. the vessel's designated owning/operating entity.
akaNamesarray of objects{ type, category, name } — Also Known As entries (type: a.k.a. or f.k.a.; category: strong or weak).
imoNumberstring | null7-digit IMO ship identification number, extracted from the id-list entry whose idType is "Vessel Registration Identification". Null when OFAC has not recorded one for this vessel — a real, documented gap; not every SDN vessel entry carries an IMO.
mmsistring | nullMaritime Mobile Service Identity number, when the idList carries an entry with idType "MMSI".
otherIdsarray of objects{ idType, idNumber } — remaining <id> entries not consumed as imoNumber/mmsi (e.g. Former Vessel Flag, Additional Sanctions Information).
callSignstring | nullVessel radio call sign, from vesselInfo.
vesselTypestring | nullVessel type, from vesselInfo (e.g. Crude Oil Tanker).
vesselFlagstring | nullFlag state, from vesselInfo.
tonnagestring | nullTonnage, from vesselInfo.
grossRegisteredTonnagestring | nullGross registered tonnage, from vesselInfo.
vesselOwnerstring | nullVessel owner, from vesselInfo.
listPublishDateRawstringThe <Publish_Date> of the SDN list snapshot this record was read from (MM/DD/YYYY) — one value shared by every entry from the same run's fetch, since OFAC's feed carries no per-entry designation date.
crossReferencedInUnConsolidatedListbooleantrue when this vessel's IMO number was also found (via free-text "IMO Number: NNNNNNN" extraction) in the UN Security Council Consolidated Sanctions List. Always false when enrichWithUnConsolidatedList was disabled, or when the vessel has no imoNumber to match on.
unConsolidatedListMatchesarray of objects{ dataId, name, referenceNumber, listedOn } — the matching UN entity/individual record(s), populated only when crossReferencedInUnConsolidatedList is true.
record_idstringOFAC-SDN-<uid> — stable across runs.
event_typestringSANCTION on first sighting of a vessel. STATUS_CHANGE if its sanctions program(s) changed since it was last seen. UPDATED if some other tracked field changed. SNAPSHOT_NO_DIFF if identical to last time (only ever delivered when onlyNew=false). DELISTED if a previously-seen vessel is no longer in the current OFAC feed (only computed when the run was a complete, untruncated census).
scraped_atstringISO-8601 timestamp of this extraction.
is_newbooleantrue if not seen in a prior run (delta mode, onlyNew input).
source_urlstringPer-vessel permalink on sanctionssearch.ofac.treas.gov (OFAC's own public Sanctions List Search tool), e.g. https://sanctionssearch.ofac.treas.gov/Details.aspx?id=15036.

A realistic dataset record for a normal (non-delisted) event — illustrative values, every field name drawn directly from the actor's real code and schema:

{
"uid": "15036",
"vesselName": "ARTAVIL",
"sdnType": "Vessel",
"programs": ["IRAN"],
"remarks": "Vessel Registration Identification IMO 9187629; Linked To: NATIONAL IRANIAN TANKER COMPANY.",
"linkedTo": "NATIONAL IRANIAN TANKER COMPANY",
"akaNames": [
{ "type": "a.k.a.", "category": "strong", "name": "ARTA" }
],
"imoNumber": "9187629",
"mmsi": "422123456",
"otherIds": [
{ "idType": "Former Vessel Flag", "idNumber": "Panama" }
],
"callSign": "EQFY",
"vesselType": "Crude Oil Tanker",
"vesselFlag": "Iran",
"tonnage": "159939",
"grossRegisteredTonnage": "160000",
"vesselOwner": "NATIONAL IRANIAN TANKER COMPANY",
"listPublishDateRaw": "09/05/2026",
"crossReferencedInUnConsolidatedList": true,
"unConsolidatedListMatches": [
{ "dataId": "QDi.123", "name": "NATIONAL IRANIAN TANKER COMPANY", "referenceNumber": "IRAN-3", "listedOn": "2012-07-31" }
],
"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"
}

A DELISTED record uses a deliberately smaller field set — no stale native fields are carried forward:

{
"record_id": "OFAC-SDN-14812",
"event_type": "DELISTED",
"uid": "14812",
"vesselName": "SEA GLORY",
"imoNumber": "9345671",
"scraped_at": "2026-09-08T14:00:00.000Z",
"is_new": false,
"source_url": "https://sanctionssearch.ofac.treas.gov/Details.aspx?id=14812",
"lastConfirmedListedAt": "2026-08-11T14:00:00.000Z"
}

Multi-language Integration Snippets

cURL

curl "https://api.apify.com/v2/acts/stefano_seggio~actor-19-maritime-sanctions-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"maxItems": 250,
"onlyNew": true,
"enrichWithUnConsolidatedList": true,
"programFilter": ["IRAN", "DPRK2"]
}'

Python (apify-client)

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("stefano_seggio/actor-19-maritime-sanctions-monitor").call(
run_input={
"maxItems": 250,
"onlyNew": True,
"enrichWithUnConsolidatedList": True,
"programFilter": ["IRAN", "DPRK2"],
}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["uid"], item["event_type"], item["vesselName"], item.get("imoNumber"))

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: 250,
onlyNew: true,
enrichWithUnConsolidatedList: true,
programFilter: ['IRAN', 'DPRK2'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
console.log(item.uid, item.event_type, item.vesselName, item.imoNumber);
}

Pricing Model Explanation

This actor bills per event ("pay-per-event"), with platform usage included in the event price — there is no separate compute charge on top.

EventPriceWhen it fires on this actor
result$0.0005 per recordEvery dataset item this run delivers — a SANCTION, STATUS_CHANGE, UPDATED, or SNAPSHOT_NO_DIFF record from the live OFAC feed, and equally a synthesized DELISTED record from persisted state.
apify-actor-start$0.00005Once per run, when the run starts.

Unlike some other actors in this fleet, there is only one billed tier here — no cheaper "listing" vs. pricier "detail" split. Per this actor's own CHANGELOG.md (2.0.0, "Not added and why"): "No pricing/monetization change. The live-configured single-tier result price ($0.0005/record) was already correct and is untouched — below the fleet's $0.003 rate card, a defensible independent choice for a simpler-per-record domain than a full tender detail page." That single flat rate reflects the source reality: both the OFAC SDN.XML feed and the UN Consolidated List XML feed are single-file, plain unauthenticated downloads with no per-record fetch cost and no proxy requirement, so there is no "extra work per record" for a two-tier split to price differently — every record, whether newly sanctioned, reclassified, or delisted, costs this actor the same amount of work to produce and is billed the same $0.0005.

On onlyNew and unchanged records: when onlyNew: true, a vessel classified SNAPSHOT_NO_DIFF (identical fingerprint to its last-seen state) is skipped by a continue in src/fetchVesselRecords.ts before Actor.pushData() or Actor.charge() ever run for it. It is never delivered to the dataset and never billed — not billed at $0, simply never created as a chargeable event in the first place. DELISTED records are the one exception that is always billed regardless of onlyNew, since a delisting is inherently new, actionable information for a compliance monitor and is never suppressed as "unchanged." Concretely: a scheduled onlyNew run against the full ~1,540-vessel register that finds, say, 3 newly sanctioned vessels, 2 program changes, and 1 delisting costs 6 × $0.0005 = $0.003 plus the $0.00005 run-start charge — you pay only for the handful of records that actually changed, not for re-delivering the unchanged majority of the list every run.