# Supplier Sanctions & Debarment Intelligence (`atlas-data/supplier-sanctions-debarment-intelligence`) Actor

Normalized World Bank debarment export, change intelligence, and conservative batch supplier screening.

- **URL**: https://apify.com/atlas-data/supplier-sanctions-debarment-intelligence.md
- **Developed by:** [Atlas](https://apify.com/atlas-data) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Supplier Sanctions & Debarment Intelligence

`supplier-sanctions-debarment-intelligence` turns the official World Bank Listing of Ineligible Firms and Individuals into a normalized, change-aware supplier feed and a transparent batch-screening workflow.

It is built for procurement compliance, supplier onboarding, bid teams, vendor-risk operations, and audit evidence. Instead of a thin table scrape, each successful run can provide:

- stable entity IDs backed by the World Bank source record ID;
- normalized names, address/country, debarment period, status, and grounds;
- deterministic deduplication without merging separate source IDs that happen to normalize alike;
- exact and conservative fuzzy screening for a supplier batch, with component scores, gates, matched tokens, and country comparison;
- full, incremental, and diff exports with additions, updates, unchanged records, and removal tombstones;
- bounded durable state, configuration isolation, single-writer fencing, and immutable snapshots.

> Scope: this Actor covers the **World Bank list only**. It is not a complete sanctions, watchlist, PEP, adverse-media, or legal screening service, and its output is not legal advice. Potential matches require human review.

### Authoritative source

The only source is the [World Bank Listing of Ineligible Firms and Individuals](https://www.worldbank.org/en/projects-operations/procurement/debarred-firms). The official page states that the list updates every three hours.

At runtime the Actor downloads that page and discovers the production gateway URL and browser API key embedded in its public JavaScript. The key is page configuration—not a private credential—and is never hard-coded, logged, written to a dataset, stored in state, or included in this repository. The discovered URL is accepted only when it exactly matches the expected HTTPS production World Bank gateway host and path.

The gateway response must contain a nonempty, bounded `response.ZPROCSUPP` array. The Actor parses the official fields including:

- `SUPP_ID`, `SUPP_NAME`, and `ADD_SUPP_INFO`;
- `SUPP_ADDR`, city/state/postal fields, `COUNTRY_NAME`, and `LAND1`;
- `DEBAR_FROM_DATE` and `DEBAR_TO_DATE`;
- `INELIGIBLY_STATUS`;
- `DEBAR_REASON`;
- `LAST_REFRESH_DATE`.

An empty, redirected, oversized, non-JSON, malformed, conflicting-duplicate, or schema-invalid response fails closed. Production also requires at least 1,500 normalized entities, a floor backed by the current official feed of roughly 1,523 rows. After the first complete run, the last complete entity count is stored and a later feed below 90% of it is rejected. This relative floor cannot be bypassed through Actor input. No dataset writes, removal inference, `OUTPUT`, or durable state commit occurs from incomplete source data.

### Input

```json
{
  "mode": "incremental",
  "suppliers": [
    {
      "referenceId": "ERP-10482",
      "name": "Example Engineering Limited",
      "country": "Kenya"
    },
    {
      "referenceId": "BID-77",
      "name": "Another Supplier"
    }
  ],
  "fuzzyThreshold": 0.9,
  "maxMatchesPerSupplier": 5,
  "includeSourceRecords": true,
  "maxSourceRecords": 5000,
  "maxScreeningSuppliers": 500,
  "stateRetentionDays": 180
}
```

#### Modes

- `full`: emits every current source entity as `snapshot` and authoritatively replaces the current digest baseline.
- `incremental`: emits only `new`, `updated`, and `removed` source entities.
- `diff`: emits current entities as `new`, `updated`, or `unchanged`, plus removals.

Every submitted supplier produces one `screening` dataset record, including explicit `no-match` results. `includeSourceRecords: false` suppresses source rows but still advances source change state after the entire successful run.

`maxSourceRecords` is a fail-closed source safety cap between 1,500 and 10,000 and participates in the durable configuration hash. Screening queries, match thresholds, output mode, and display choices do not fork source change history. Supplier `referenceId` values must be unique within a run.

### Matching behavior

Exact matching compares the full supplier name after deterministic Unicode, case, punctuation, ampersand, and whitespace normalization.

Fuzzy matching removes common trailing legal-form tokens for comparison and combines:

- character-bigram Dice similarity;
- exact token Jaccard overlap;
- leading-prefix agreement.

A score alone is not enough. Conservative hard gates block explicit country conflicts, very short queries, weak single-token names, multi-token names with no exact token overlap, and numeric-token conflicts. The default threshold is `0.90` and cannot be set below `0.85`. Results are candidates for review, not automated clearance decisions.

Country comparison uses the provided country text and the World Bank country name after the same text normalization. Use the official country name when possible.

### Dataset output (schema `1.0.0`)

The dataset contains two record kinds.

#### `source`

Key fields:

- `entityId`: SHA-256 of the source namespace and World Bank `SUPP_ID`; stable across name/address edits;
- `emissionId`: retry-stable SHA-256 idempotency key for this logical source emission;
- `sourceRecordId`: original World Bank `SUPP_ID`;
- `entityKey`: normalized name/country clustering hint; never used to merge distinct World Bank IDs;
- `name`, `additionalInformation`, normalized name forms, and normalized address;
- `debarment`: `from`, `to`, optional source `status`, `reason`, and `isOpenEnded`;
- `contentDigest`, `changeType`, and field-level `changedFields`;
- `sourceUpdatedAt`, Actor `fetchedAt`, and attribution/disclaimer.

A `removed` record is a tombstone carrying the last durable source representation. Removal means absent from a later complete World Bank response for the same configuration; it does not independently establish that sanctions or debarment no longer apply.

#### `screening`

Each input supplier produces:

- retry-stable `emissionId` for this logical screening result;
- stable `screeningId` and caller `referenceId`;
- original and normalized query;
- `matched` or `no-match`;
- bounded matches with World Bank identity/debarment data;
- `exact` or `fuzzy` match type and deterministic score;
- component scores, matched tokens, country comparison, and passed gates;
- for no-match rows, the best rejected candidate and rejection reasons when available.

The `OUTPUT` key contains the pre-commit run summary, source refresh time, change totals, screening totals, dataset ID, attribution, and the explicit last-state-commit policy. It is intentionally persisted with `status: "pending-state-commit"` and `stateCommitStatus: "pending"` because it must be written before state. Only the in-memory return value and successful Actor run report `succeeded` after the final state save. If state save fails, the Actor fails and persisted `OUTPUT` remains honestly pending.

### Durable consistency and concurrency

State is stored in the named key-value store `supplier-sanctions-debarment-intelligence-state-v1`, isolated from ephemeral run storage. A named request queue provides a five-minute renewable writer lease. The adapter acquires an authoritative expiry once and renews only within the final half of the lease; frequent per-record fence checks therefore do not repeatedly extend the remote lock. A run that cannot acquire or renew the lease refuses to write, and a locally expired writer is rejected without contacting storage.

Successful state writes create immutable, generation-stamped snapshots; old snapshots are retained up to a bounded count. The state document has an 8 MB safety budget, at most four configurations, and at most 10,000 digest entries per configuration.
Before the final authoritative snapshot, state save checks the cached expiry and prolongs only when it is near expiry. After a successful snapshot, the run clears local lease ownership and attempts a best-effort queue unlock so another run can acquire immediately. Queue unlock is cleanup rather than another data/state commit: unlock failure is swallowed after local fencing, does not reverse success, and the last authoritative lease expiry remains the bounded fallback. Failed runs also release a still-valid lease on a best-effort basis.

Commit order is deliberate:

1. write all dataset records;
2. write `OUTPUT`;
3. assert the writer fence and write the **last** immutable state snapshot;
4. clear local lease ownership and attempt a non-throwing queue unlock as cleanup.

If a dataset or `OUTPUT` write fails, state does not advance. If the last state write fails, a retry may have a different observation-only `fetchedAt`, but every source and screening dataset row carries a deterministic `emissionId` that is stable for the same uncommitted baseline and logical result. Consumers should deduplicate or upsert by `emissionId`; do not use `fetchedAt` as an idempotency key.

### Reliability and limits

Both official requests use 30-second timeouts, bounded exponential backoff with jitter, and up to four retries for network errors, HTTP 429, and HTTP 5xx. `Retry-After` seconds and HTTP dates are honored up to 30 seconds. Redirects from the gateway are refused. HTML and JSON bodies are streamed through 5 MB and 20 MB caps.

The source currently arrives in one gateway response, so there is no invented pagination contract. All source records are normalized and checked against the absolute and prior-complete-count floors before removal inference or writes begin. Supplier screening is deterministic and bounded to 500 queries, 20 returned matches per query, and 10,000 source rows.

### Cost honesty

Each run normally makes two public HTTP requests: one official page request and one official gateway request, plus bounded retries when needed. Matching is local CPU work proportional to `supplier count × current World Bank rows`; a 500-supplier batch compares against the full list. Apify charges depend on your platform plan, run duration, memory, dataset writes, and storage retention. This Actor cannot promise a fixed run price. Incremental mode can reduce dataset writes, while screening cost remains proportional to the submitted batch.

Submitted supplier names and optional countries are written to the run dataset as audit evidence. Do not submit data you are not authorized to process.

### Development and verification

Requires Node.js 22.

```bash
npm ci
npm run lint
npm run typecheck
npm run build
npm test
npm run smoke:local
npm run smoke:live
npm run validate:schema
npm audit --audit-level=high
npm run audit:source
```

`npm run check` runs every non-network check. The adversarial suite covers discovery changes, hostile/ambiguous discovery, response envelopes, absolute and relative completeness floors, malformed rows/JSON, retry exhaustion and `Retry-After`, normalization collisions, fuzzy false positives, caps, configuration isolation, authoritative full-baseline replacement, retry-stable emission IDs, honest pre-commit OUTPUT, write ordering/failures, lease contention/expiry, final-write snapshots, schema validation, and a complete local smoke pipeline. The opt-in live smoke uses only the official World Bank page and gateway.

#### Local Actor smoke

After building, run through the simulated Apify environment:

```bash
npx apify run --purge --input '{"mode":"full","suppliers":[],"includeSourceRecords":false}'
```

You can also place input in local storage and run `npx apify run`.

### Attribution

Source: World Bank, “Listing of Ineligible Firms and Individuals.” The World Bank controls the source content, update timing, availability, and interpretation. Review the official page, applicable World Bank rules, and qualified legal/compliance guidance before making procurement or eligibility decisions.

# Actor input Schema

## `mode` (type: `string`):

Full emits a snapshot; incremental emits additions, updates, and removals; diff also emits unchanged source entities.

## `suppliers` (type: `array`):

JSON array of { referenceId, name, country? }. referenceId must be unique. Exact and conservative fuzzy screening always run.

## `fuzzyThreshold` (type: `number`):

Minimum weighted score after all hard gates pass. Higher means fewer possible matches.

## `maxMatchesPerSupplier` (type: `integer`):

Bounds candidate evidence returned for each submitted supplier.

## `includeSourceRecords` (type: `boolean`):

When false, only screening rows are written, but durable change state still advances after success.

## `maxSourceRecords` (type: `integer`):

Fail closed if the official response grows beyond this bound; no removals or state are committed.

## `maxScreeningSuppliers` (type: `integer`):

Must be at least the number of suppliers and never exceeds 500.

## `stateRetentionDays` (type: `integer`):

Upper bound for inactive digest retention; current source entities are always retained.

## Actor input object example

```json
{
  "mode": "incremental",
  "suppliers": [],
  "fuzzyThreshold": 0.9,
  "maxMatchesPerSupplier": 5,
  "includeSourceRecords": true,
  "maxSourceRecords": 5000,
  "maxScreeningSuppliers": 500,
  "stateRetentionDays": 180
}
```

# Actor output Schema

## `dataset` (type: `string`):

Versioned normalized World Bank source exports/removals plus transparent batch screening results.

## `summary` (type: `string`):

Pre-commit counts, change classification, source refresh time, attribution, and pending state-commit status. Actor run success is authoritative.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("atlas-data/supplier-sanctions-debarment-intelligence").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("atlas-data/supplier-sanctions-debarment-intelligence").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call atlas-data/supplier-sanctions-debarment-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atlas-data/supplier-sanctions-debarment-intelligence"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/VJuATZVHLe3L7FRZk/builds/9MXOGTIp4KxSJwYg6/openapi.json
