Supplier Risk Monitor avatar

Supplier Risk Monitor

Under maintenance

Pricing

from $3.00 / 1,000 register checks

Go to Apify Store
Supplier Risk Monitor

Supplier Risk Monitor

Under maintenance

Re-check a saved supplier list. Register, VAT, sanctions, ownership, domain expiry and US SAM exclusions are billed per supplier. A change alert is billed only when the risk changes. A failed source is never clear.

Pricing

from $3.00 / 1,000 register checks

Rating

0.0

(0)

Developer

Mike Berckmoes

Mike Berckmoes

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

8 days ago

Last modified

Categories

Security

Limited permissions

Share

Re-check a saved list of suppliers on a schedule. One run reads the company register, VAT, sanctions lists, ownership, domain expiry and US SAM exclusions you turn on. The dataset has one baseline row per supplier the first time that supplier is seen, and after that only a supplier whose material result changed.

Saved state is one snapshot per supplier, in the named key-value store supplier-risk-<customerId>. It is not one global watermark. The first run stores that snapshot and does not charge a change alert.

This is a screening aid. It is not legal advice, not a credit rating, and not a clearance to contract. A failed source is never reported as clear.

What it does

  • Calls the existing register, VAT, sanctions, ownership, RDAP and SAM functions in this container. No child Actor is started, so those Actors are not billed again.
  • Charges one event per supplier per check that called a source and got an answer. A failed source is not charged. A check that does not apply (no VAT number, no UEI, no domain) is not charged.
  • Charges change-alert only when a saved snapshot changes. The baseline, an unchanged re-check, and a newly enabled check that comes back clean are not change alerts.
  • France also reads BODACC for dissolution and insolvency-style notices as part of the register check (90-day window). The UK profile is read only when you set a Companies House key. The Companies House filing stream is not used.
  • Sanctions lists are cached in supplier-risk-sanctions-cache for listMaxAgeHours (default 24). A refresh sends If-None-Match or If-Modified-Since when the last response included an ETag or Last-Modified. The cache is never used once it is older than 72 hours, including when the download fails. A list that failed to load is not treated as fresh.
  • Does not send e-mail. An optional https webhook posts only when there is a real change. localhost, an IP address, an internal or metadata host, a port other than 443, and an @ in the host are rejected, and the client does not follow redirects. generic posts the alert JSON, slack posts {"text"}, discord posts {"content"}. A webhook password is sent only as Authorization: Bearer and is not stored.
  • The insolvency check is a plug for a later Actor (my_actor/checks/insolvency.py). It does not call a source yet. Turning it on is not billed and does not produce a clear result.
  • At most 50 suppliers per run (default 25). The run is pinned to 512 MB because a cold sanctions load needs that much.

Use cases

  • Weekly re-check of a procurement supplier list, with a Slack or Discord message only when a supplier's risk changes.
  • Register and VAT on a French supplier, with sanctions on the same name.
  • Domain expiry on the supplier's website, without an alert every day the countdown moves.
  • SAM.gov exclusion check for a US supplier you already know by UEI or CAGE.
  • Ownership parent check when you opt in, using the same GLEIF walk as Company Ownership Lookup.

What you get

FieldMeaningExample
customerIdCustomer this row belongs toacme-procurement
supplierIdId from the input, or a hash of the identifiersexample-fr
nameName from the input. Null when the input had noneExample SAS
countryISO 3166-1 alpha-2. Null when the input had noneFR
eventTypebaseline the first time this supplier is stored, change after that. An unchanged supplier is not a dataset rowbaseline
riskclear, review, alert, or unknownclear
severityclear, unknown, low, medium, high, or criticalclear
failClosedtrue when an enabled check's source failed. The row is never clear in that casefalse
summaryOne short line per check that ranregister: Register active.
changesEmpty on a baseline. On a change, one object per material field that differed: check, field, before, after, severity[]
checksOne object per enabled check. status is skipped_unpriced when that event has no live price: the source is not called, the check has no data, and it is not charged. See the check fields belowregister clear
checkedAtWhen this supplier was checked. Filled at run timeISO 8601 UTC
disclaimerScreening aid. Not legal advice and not a clearancethe fixed disclaimer

Every check object has status, severity, detail and charged. status is clear, review, hit, not-found, source-failed or not-applicable. charged is true only when that check called a source and the event was in the live price list. Extra fields:

CheckExtra fields
registerregisterSource, registrationNumber, registerStatus, legalName, notice
vatvatNumber, vatExistence, vatName
sanctionshitCount, listsPartial. A hit also has topScore, topList, topUid, and topEntityType. A person hit gives the list name and the entry id, not the person's name
ownershiplei, ultimateParent, parentCount, pscStatus, warnings. A miss has ownershipStatus
domaindomain, registered, registrar, expiresAt, daysToExpiry
samueiSAM, cageCode, exclusionFlag, exclusionCount, registrationStatus
insolvencyprovider. The plug sets this to null

RUN_SUMMARY on the default key-value store has the counts and the state-store name. ALERT is written there only when this run had a change. The per-supplier snapshot is supplier-<supplierId> in supplier-risk-<customerId>.

A row is clear only when every applicable check succeeded and is clean. not-applicable (no VAT number, no domain, no UEI) does not block clear. If every enabled check is not-applicable, the risk is unknown.

How to use it

  1. Set customerId to a stable name and paste the supplier list.
  2. Leave the checks you want on. Ownership and insolvency are off unless you turn them on.
  3. Schedule the same input (for example weekly). The next run reuses supplier-risk-<customerId>. To wipe the snapshots, delete that named key-value store in the Apify Console under Storage → Key-value stores. The next run stores baselines again.
  4. Read the dataset for baselines and later changes, and RUN_SUMMARY for counts. On a change, read ALERT or the webhook.
  5. For a UK profile or UK PSC, set companiesHouseApiKey. For SAM exclusions, set samGovApiKey and a UEI or CAGE.

The first run pushes the baseline and charges the checks that ran. It does not charge change-alert. A later run with the same material pushes nothing and still charges the checks that ran.

Input

FieldWhat to enter
customerIdStable id. Letters, numbers, _ and -. This selects the state store.
suppliersObjects with supplierId, name, country, registrationNumber, vatNumber, lei, domain, ueiSAM, cageCode. An empty identifier row is skipped.
maxSuppliers1 to 50. Default 25. Rows past the cap are not checked and are not stored as clear.
checksBooleans. Defaults: register, VAT, sanctions, domain and SAM on. Ownership and insolvency off.
matchModestrict (default), balanced, or loose.
minScoreSanctions score cutoff. Default 80. Range 50 to 100.
expiryWithinDaysDomain or SAM expiry inside this many days is medium. Default 30.
listMaxAgeHoursSanctions cache age. Default 24. Range 1 to 168. A cache older than 72 hours is never used.
refreshListsDownload sanctions lists again. Default false.
resetStateNext rows are baselines again. Default false.
companiesHouseApiKeyOptional. UK profile and UK PSC. Or set COMPANIES_HOUSE_API_KEY.
samGovApiKeyOptional. Required for a UEI or CAGE check. Or set SAM_GOV_API_KEY.
webhookUrlOptional public https:// URL. localhost, an IP address, an internal or metadata host, a port other than 443, and an @ in the host are rejected. Called only for a real change. Redirects are not followed.
webhookFormatgeneric, slack, or discord.
webhookSecretOptional password, sent as a Bearer token. Not stored.

An empty customerId, an empty supplier list, a webhook that is not a public https:// host, or a password without a URL stops the run before any fetch and before any charge.

{
"customerId": "acme-procurement",
"maxSuppliers": 25,
"checks": {
"register": true,
"vat": true,
"sanctions": true,
"ownership": false,
"domain": true,
"sam": true,
"insolvency": false
},
"suppliers": [
{
"supplierId": "example-fr",
"name": "Example SAS",
"country": "FR",
"registrationNumber": "380000000",
"vatNumber": "FR43380000000",
"domain": "example.com"
}
]
}

Output example

Offline baseline for the Acme fixture in tests/fixtures/fr_acme.json (register active, VAT present, sanctions clear, domain registered). checkedAt is filled at run time.

{
"customerId": "acme",
"supplierId": "acme-fr",
"name": "Acme France SAS",
"country": "FR",
"eventType": "baseline",
"severity": "clear",
"risk": "clear",
"failClosed": false,
"summary": "register: Register active. vat: VAT number exists. sanctions: No sanctions hit at the chosen score. domain: Domain is registered and outside the expiry window. sam: SAM exclusions run when a UEI or CAGE is set.",
"changes": [],
"checks": {
"register": {
"status": "clear",
"severity": "clear",
"detail": "Register active.",
"charged": true,
"registerSource": "FR_SIRENE",
"registrationNumber": "380000000",
"registerStatus": "active",
"legalName": "Acme France SAS",
"notice": "none"
},
"vat": {
"status": "clear",
"severity": "clear",
"detail": "VAT number exists.",
"charged": true,
"vatNumber": "FR43380000000",
"vatExistence": "exists",
"vatName": "Acme France SAS"
},
"sanctions": {
"status": "clear",
"severity": "clear",
"detail": "No sanctions hit at the chosen score.",
"charged": true,
"hitCount": 0,
"listsPartial": false
},
"domain": {
"status": "clear",
"severity": "clear",
"detail": "Domain is registered and outside the expiry window.",
"charged": true,
"domain": "acme-test.com",
"registered": true,
"registrar": "Example Registrar",
"expiresAt": "2035-01-01T00:00:00Z",
"daysToExpiry": 3000
},
"sam": {
"status": "not-applicable",
"severity": "clear",
"detail": "SAM exclusions run when a UEI or CAGE is set.",
"charged": false
}
},
"checkedAt": "filled at run time",
"disclaimer": "Screening aid only. Not legal advice, not a credit rating, and not a clearance to contract. A failed source is never a clear result."
}

checkedAt and daysToExpiry are filled at run time. The other fields are the ones this fixture writes.

A later inactive register is eventType: "change", risk: "review", severity: "high", with changes showing registerStatus from active to inactive.

Pricing

Pay per event. The live price is .actor/pricing.json. One supplier on all seven checks is $0.025. The run reserves one change-alert, so one supplier on all checks is $0.028 (minimalMaxTotalChargeUsd).

Apify keeps 20% of the event price. Platform compute is $0.20 per compute unit (1 compute unit = 1 GB-hour), from the sanctions check run recorded in actors/BUILD-NOTES.md (0.00702 CU, compute line $0.001404). This Actor is pinned to 512 MB, so the developer pays that compute.

EventPriceKept after 20%Compute this price covers
register-check$0.003$0.002430 s at 512 MB is about $0.00083. The measured status-watch check was 7.22 s.
vat-check$0.003$0.0024Same 30 s floor.
sanctions-check$0.006$0.0048Measured cold run: 50.5 s at 512 MB, about $0.00140.
ownership-check$0.004$0.0032Measured 9.06 s (that run was 256 MB). At 512 MB the same wall time is about $0.00025.
domain-check$0.003$0.002430 s floor, about $0.00083.
sam-check$0.003$0.002430 s floor, about $0.00083.
insolvency-check$0.003$0.0024Charged only when a future source is actually called.
change-alert$0.003$0.0024Extra, only when the snapshot changed. Not required to cover the re-check.

A 1-supplier run of any single check above is profitable after the 20% cut, including a sanctions-only cold run and a 60-second run of a $0.003 check (compute about $0.00167, kept $0.0024).

A 1-supplier run with all seven checks, one after another, uses the sum of those times (about 210 s) and about $0.0058 of compute. The seven check events are $0.025 and keep $0.020. With the reserved change-alert ($0.003) that is $0.028. An unchanged week still charges the checks and does not charge change-alert. A check that flips between clear and skipped_unpriced does not charge a change alert, does not call the webhook, and does not replace the saved material.

Not charged: a failed source, a check that does not apply, the unconfigured insolvency plug, the baseline as an alert, an unchanged supplier row, and a dataset row by itself. A requested check with no priced event is skipped, not returned for free. Its status is skipped_unpriced: no source call, no data, and no charge, so the supplier is not clear. An unpriced change-alert is not charged and does not call the webhook; the change row is still saved. The other priced events for that supplier still run. There is no start fee.

Integrations

  • Schedule the same customerId and the same supplier list. Weekly is the intended cadence.
  • Export the dataset as JSON, CSV, Excel or HTML.
  • Read ALERT on the default key-value store when you want one record per run that had a change. It is absent when nothing changed.
  • Optional webhook above, for Slack, Discord, or a generic JSON receiver. This Actor does not post one webhook per unchanged supplier.
  • A platform webhook on run succeeded still fires every run. Use ALERT or the dataset if you only want changes.
  • Call it with the Apify API or apify-client. Pass the same input the schedule uses.

FAQ

Does a failed source come back as clear? No. That check is source-failed, failClosed is true, and risk is unknown unless a real problem (medium or above) was also found.

What is charged when nothing changed? The checks that called a source and got an answer. A failed source is not charged. Not a change alert, and no dataset row.

What happens the first time I enable a new check? A clean result is stored and is not a change alert. A hit, a failed source, or a medium-or-higher result is a change alert.

Does the insolvency check do anything yet? It reserves the check and fails closed when you turn it on. Wire the later Actor in my_actor/checks/insolvency.py. Until worked is true, it is not billed.

Does EU Company Status Watch already send this webhook? No. This Actor posts the optional password-protected webhook itself. The status watch does not.

Is a sanctions clear a legal clearance? No. A clear result means this run found no name at or above minScore on the lists that loaded. Confirm hits on the official list.

Limits

  • 50 suppliers per run. Raise the cap in the input only up to 50. A longer list is truncated, those suppliers are not checked, and they are not given a clear row. A large list may need two scheduled runs, each with its own slice of suppliers. A run that stops at the charge cap leaves the suppliers it did not save for the next schedule.
  • Sanctions lists are downloaded at most once per cache window, and never served from a cache older than 72 hours. A conditional request is used when the source supports ETag or Last-Modified. Natural-person names in that cache are SHA-256 hashes of the folded name. A later run matches an individual only when the full name is exact, so fuzzy, token, and reordered-name matches are lost. The hash is unsalted, so it hides the name in the cache but is not anonymisation. A cold load is the slow part of a 1-supplier run. Later runs in the same day reuse the cache.
  • Register lookups and SAM/UEI calls are not cached. Companies House and BODACC can answer 304, but this Actor does not keep those validators. A repeat run makes the same register and SAM calls as a cold run, and those checks cost the same.
  • French notices are the last 90 days of BODACC, not the full archive. A notice older than that is not a current finding.
  • UK Companies House is a profile read, not the 60–90 second filing stream. Without your key, a GB company can still match on GLEIF, and UK PSC is skipped.
  • SAM exclusions run only for a UEI or CAGE, with your api.data.gov key. A US supplier with only a name is not-applicable for SAM.
  • Domain alerts are registration, expiry band, expiry date and registrar. A nameserver-only change is not an alert. Some TLDs have no RDAP server in the bundled route and come back source-failed.
  • The run does not crawl a website and does not send e-mail.

Sources: the company registers and GLEIF used by EU Company Register Lookup, VIES and the national VAT checks used by EU TIN VAT Validator, OFAC, the UK FCDO list, the UN consolidated list and the EU FSF file, GLEIF Level 2 parents (and UK PSC when you supply a key), the IANA RDAP bootstrap, BODACC annonces commerciales, and SAM.gov entity and exclusions. This Actor is not the official product of any of those sources.