# ISO Certificate Check (`mikee368/iso-certificate-check`) Actor

Check an ISO 9001, 14001, 27001, or 45001 certificate number against a local shape check and the official manual pages. A live result is returned only when you supply your own IAF CertSearch API key. Accreditation registers are not scraped.

- **URL**: https://apify.com/mikee368/iso-certificate-check.md
- **Developed by:** [Mike Berckmoes](https://apify.com/mikee368) (community)
- **Categories:** Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.50 / 1,000 certificate checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## ISO Certificate Check

Check a supplier's management-system certificate (ISO 9001, ISO 14001, ISO 27001, ISO 45001, and the other numbers listed below). A run returns one check row per certificate. In watch mode it can also return an alert when a later run, with the same Monitor ID, sees a suspension, a withdrawal, an expiry, or a status change.

The live certificate call is the [IAF CertSearch Verification Users API](https://support.iafcertsearch.org/verification-users-api-developer-guide), and only when you paste your own API key. This Actor does not scrape IAF CertSearch, DNV, BSI, TÜV, Bureau Veritas, SGS, LRQA, UKAS, RvA, DAkkS, or ANAB. Those sites' terms do not allow it. The research note is `docs/iso-cert-sources-terms.md`.

Without a key, the row is a local certificate-number shape check, a recognised issuer name when we have one, and links to the official manual pages. That row is `cannot_verify_automatically`. A plausible number is not a valid certificate. `not_live_checked` is not an accreditation.

This Actor is not IAF, UKAS, RvA, DAkkS, ANAB, or any certification body, and it is not endorsed by them.

### What was excluded

No adapter calls these sites. The reason for each one is in `docs/iso-cert-sources-terms.md`.

- IAF CertSearch website. The terms prohibit scraping and commercial reproduction. The free search is capped (the FAQ says 3 validations a day for a public user and 30 for a free account).
- DNV CertChecker, the BSI client directory, TÜV SÜD's certificate finder, TÜV Rheinland Certipedia, Bureau Veritas, the SGS client directory, and the LRQA verification form. No public API and no grant of automated commercial use. Bureau Veritas, BSI, and DNV say so in their own terms.
- UKAS (including CertCheck), RvA, DAkkS, and ANAB. Used only as manual links. ANAB's terms forbid copying the directory into a database. UKAS CertCheck is built to stop scraping. RvA and DAkkS publish registers and do not publish an API or a commercial extraction licence.

The permitted live path is the IAF subscriber API. The licence is the subscriber's, it cannot be sublicensed, and IAF may add a charge if the subscriber shares verification products with third parties. You supply the key. This Actor does not ship one.

### What it does

- Reads a supplier name, a certificate number, a standard, and an issuing body. One certificate uses those fields. A batch uses **Certificates**. At most 200 certificates per run, in input order. Further rows are not checked.
- With no API key, writes `cannot_verify_automatically`, `certificateNumberPlausibility` (`plausible` or `unexpected`), and `manualVerificationUrl`. Recognised brands are BSI, DNV, TÜV SÜD, TÜV Rheinland, TÜV NORD, Bureau Veritas, SGS, and LRQA. Any other name still gets the IAF CertSearch link.
- Always lists the manual accreditation pages for UKAS, UKAS CertCheck, RvA, DAkkS, and ANAB. It does not request them. `issuerAccreditationStatus` stays `not_live_checked` until an IAF exact match names a body.
- With your key, sends `POST https://<your base>/company` for one supplier and country. `match_rate` is 94. Only `exactly_matched` can become verified, expired, suspended, or withdrawn. A loose match stays `cannot_verify_automatically` and does not copy scope or dates. A later watch run sends `GET /company/{ce_id}` for an id stored from an earlier exact match.
- One IAF call covers every certificate you asked about for that supplier. IAF bills your account one credit for an exact company match, not one credit per certificate. Those credits are not part of this Actor's price.
- Stops further IAF calls for the run after HTTP 429 or `api_request_limit_reached`. One retry, then the remaining certificates are `iaf_rate_limited`.
- Charges `certificate-checked` only for a stored row whose IAF result is `verified`. A run with no key, a failed or errored lookup, and every other status are stored free. A watch alert is a separate dataset row and is not charged. The first watch observation of a verified certificate is charged and does not emit an alert.

### Use cases

- Prepare a due-diligence pack that says the certificate was not verified automatically, and that points a reviewer at the issuer's own page and the accreditation registers.
- Verify a supplier when you already pay for IAF CertSearch. Paste the key and the API base URL from your IAF API settings.
- Watch a portfolio. Schedule Watch with the same Monitor ID. The next run alerts on suspension, withdrawal, expiry, or another status change, and can POST one HTTPS webhook for that run's alerts.

### What you get

A check row sets `kind` to `check`. `verificationStatus` is one of `verified`, `not_found`, `expired`, `suspended`, `withdrawn`, or `cannot_verify_automatically`.

`verified`, `expired`, `suspended`, and `withdrawn` are used only after an IAF exact match. `not_found` means IAF returned no company, or the company record has no certificate with that number. `cannot_verify_automatically` covers no key, a missing country, a loose name match, an issuer or standard mismatch, an unrecognised status, and an API error.

`issuerAccreditationStatus` is `iaf_mla` when the matched IAF record flags the standard as MLA-accredited, `named_by_iaf_record` when the record names an accreditation body without that flag, and `not_live_checked` otherwise.

An alert row sets `kind` to `alert` and sets `alertType` to `SUSPENDED`, `EXPIRED`, `WITHDRAWN`, `STATUS_CHANGE`, or `EXPIRY_SOON`. The first time a certificate is stored, no alert is written.

| Field | Meaning | Example |
|---|---|---|
| `verificationStatus` | Result of this check | `cannot_verify_automatically` |
| `verificationDetail` | Short reason | `no_live_source_permitted` |
| `issuerAccreditationStatus` | From an IAF record, or not checked live | `not_live_checked` |
| `expiresAt`, `validFrom`, `scope` | From an exact IAF match. `expiresAt` can also be a date you typed | null on a local row |
| `certificateNumberPlausibility` | Local shape: `plausible` or `unexpected` | `plausible` |
| `manualVerificationUrl` | Issuer page, or IAF CertSearch | the BSI directory when the issuer is BSI |
| `manualAccreditationUrls` | UKAS, CertCheck, RvA, DAkkS, ANAB | five links, not queried |
| `alertType` | Watch alert. Set on an alert row only | absent on a check row |

`RUN_SUMMARY` holds the mode, the Monitor ID, how many checks were charged, the alert types, and how many IAF calls were made. The API key is recorded only as `set` or null.

### How to use it

1. Enter the supplier, the certificate number, the standard, and the issuing body. Or put a batch in **Certificates**.
2. Leave **IAF CertSearch API key** empty unless you have a Verification Users subscription. If you set the key, also set **Supplier country**. Copy **IAF API base URL** from your IAF API settings when it differs from the prefill.
3. Choose **Mode**. Lookup does not save watch state. Watch writes `seen-<monitorId>` and `meta-<monitorId>` in the named store `iso-cert-watch-state`.
4. Run. A verified IAF result is one `certificate-checked` event. A run with no key, and any result that is not verified, is stored and not charged. An empty input finishes with nothing charged.
5. To watch, schedule the same certificates and the same Monitor ID. Alerts start on the second run. Set **Webhook URL** to an HTTPS address if you want one POST when that run has alerts.

### Input

| Field | What to enter |
|---|---|
| Supplier name, certificate number, standard, issuing body | One certificate. A string is trimmed. Both the name and the number empty, with an empty batch, finishes with no rows. |
| Supplier country | Required for an IAF company call. Alpha-2 or a common name. |
| Known expiry date | Optional `YYYY-MM-DD` you already have. It does not verify the certificate. Watch can raise `EXPIRY_SOON` from an IAF expiry date. |
| Certificates | JSON list of the same fields. Combined with the single fields. Cap 200. |
| Mode | `lookup` (default) or `watch`. Anything else stops the run. Nothing is charged. |
| Monitor ID | Default `default`. Letters, digits, and hyphens. Other characters become hyphens. Cut at 60. |
| Expiry window (days) | 1 to 3650, default 30. |
| IAF CertSearch API key | Your key. Stored as a secret. Never written to the dataset, the summary, or the watch record. |
| IAF API base URL | Default `https://api.iafcertsearch.org/api/client/v1`. Must be HTTPS when a key is set. |
| Webhook URL | HTTPS only. Used in watch mode when there is at least one alert. |
| Delay between IAF requests (ms) | 0 to 5000, default 200. |

A bad mode, a non-list batch, a non-integer window or delay, a non-HTTPS webhook, or a non-HTTPS API base with a key stops the run before any request. Nothing is charged.

```json
{
  "supplierName": "Northwind Components",
  "certificateNumber": "FM 583920",
  "standard": "ISO 9001",
  "issuingBody": "BSI",
  "country": "GB",
  "mode": "lookup"
}
```

### Output example

This is the local row for that input. No API key, so nothing was fetched. It is not a statement that the certificate exists.

```json
{
  "kind": "check",
  "supplierName": "Northwind Components",
  "certificateNumber": "FM 583920",
  "standard": "ISO 9001",
  "issuingBody": "BSI",
  "issuingBodyId": "bsi",
  "verificationStatus": "cannot_verify_automatically",
  "verificationDetail": "no_live_source_permitted",
  "issuerAccreditationStatus": "not_live_checked",
  "certificateNumberPlausibility": "plausible",
  "source": "local",
  "scope": null,
  "manualVerificationUrl": "https://www.bsigroup.com/en-GB/products-and-services/assessment-and-certification/validation-and-verification/",
  "iafSearchUrl": "https://www.iafcertsearch.org/"
}
```

An exact IAF match can instead set `verificationStatus` to `verified`, `scope` and the dates from the API, and `issuerAccreditationStatus` to `iaf_mla` when the record says so. A loose match does not.

### Pricing

Pay per event. The live price is `.actor/pricing.json`.

The event is **`certificate-checked` at $0.01**. It is charged only for a verified IAF result.

For example, 100 verified IAF results cost the buyer $1.00. A run with no key, and any result that is not verified, costs $0. Apify's store terms pay the Actor creator 80% of those event fees, then deduct platform usage caused by paying users. Free-plan usage is covered by Apify and is not in this cost. Formula from Apify's pricing docs: `profit = (0.8 * revenue) - platform costs`. Compute here is 128 MB, so one hour is 0.125 CU. The developer CU rate used below is $0.20 (the rate Apify documents for the free and Bronze tiers when the Actor does not offer tier discounts). No proxy.

Measured on this machine, one local check (no IAF call) took 0.566 seconds of wall time, of which almost all is process start. Two hundred local checks in one process took 0.696 seconds. The extra 199 checks added about 0.13 seconds.

Cost of that one-check run at 128 MB: `0.125 * (0.566 / 3600) * 0.20`, about **$0.0000039**. The 200-check run is about **$0.0000048** in total. A slow IAF call is bounded by a 20 second HTTP timeout and one retry, about 40 seconds for one company. That run costs about **$0.00028**. A full 120 second run, the timeout in `.actor/deploy.json`, costs about **$0.00083**.

At $0.01, the creator keeps $0.008 on one verified result before platform cost. After the 40 second IAF case, creator profit is about **$0.0077**. After a 120 second run that still stored one verified result, creator profit is about **$0.0072**. Dataset writes are $0.005 per 1,000 and do not move this. The price stays above cost for a paying user. The platform's `apify-actor-start` event stays at its default $0.00005 (that comps the first 5 seconds). `apify-default-dataset-item` is not priced, so a run with no key and a result that is not verified stay free. IAF's own credits, if you set a key, are billed by IAF to that account and are not in the $0.01.

Not charged:

- A run with no IAF key. The local row is stored.
- A failed or errored IAF lookup, including 401, 429, 5xx, an empty body, and a missing country.
- Any stored status other than `verified`: not found, expired, suspended, withdrawn, a loose match, or an issuer or standard mismatch.
- A run that stops on invalid input.
- An empty certificate list.
- A watch alert row.
- Certificates past the 200 cap. They are not checked.
- A verified result that does not fit the run's charge limit. That row is not stored and is not charged, and its watch state is not updated. Further certificates in that run are not stored.

The default run memory is 128 MB. The timeout in `.actor/deploy.json` is 120 seconds.

### Integrations

- **Schedule.** Schedule Watch with the same Monitor ID. A new Monitor ID starts a new snapshot. The first observation of a verified certificate is a charged check and does not alert. A first observation with no key is free.
- **Export.** Download the dataset as JSON, CSV, Excel, or HTML.
- **Webhook.** Set **Webhook URL** to an HTTPS endpoint. Watch mode sends one JSON body when the run has at least one alert. The body has the Monitor ID, the alert count, and the alert type, supplier, certificate number, standard, status, and days to expiry. It does not include the API key.
- **API.** Start a run and read the dataset with the Apify API. The input is the JSON above.
- **RUN_SUMMARY.** Read `RUN_SUMMARY` in the default key-value store when the run finishes.

### FAQ

**Does a plausible certificate number mean the supplier is certified?**
No. It means the characters and length fit the local shape check. Validation is the issuer's page, or an exact IAF match when you use your own key.

**Why is there no live result without a key?**
The certificate databases and accreditation registers reviewed do not allow automated commercial access. The exception is the IAF CertSearch Verification Users API, which needs your own key. The Actor does not scrape CertSearch or any certification body.

**What does not_live_checked mean?**
This run did not query UKAS, RvA, DAkkS or ANAB. It does not mean the issuer is not accredited. Open the links on the row.

**What does a loose IAF match return?**
cannot_verify_automatically. A loose match is not a verification.

**Which standards can I check?**
ISO 9001, 14001, 27001 and 45001 and the other numbers listed in the README, one row per certificate, up to 200 per run.

**Can it watch for expiry or suspension?**
Yes, in watch mode with the same Monitor ID. It can emit alerts such as EXPIRY_SOON, suspension, withdrawal or status change; verified expiry data needs your IAF key.

**How is it priced?**
$0.01 per certificate-checked event on the Free plan; see the Pricing tab for when it applies.

### Limits

At most 200 certificates are checked per run. An environment cap (`ISO_CERT_MAX`) can only lower that number. Names past the cap are not checked, and the same input does not rotate them in next time.

IAF is one company per request. Several certificates for the same supplier and country share that request. HTTP 429 stops the rest of the run's IAF calls. Redirects are not followed.

The API base URL in the IAF account settings is the one that matters. The prefill is the host documented for the client API. If your account shows a different HTTPS URL, set **IAF API base URL**.

Watch state is one record per certificate inside one Monitor ID. Lookup mode does not save it. `EXPIRY_SOON` fires once per expiry date, and only while the status stays `verified` and the whole days until expiry are from 0 through the window.

Source for a live row: your IAF CertSearch subscription, called with your key. This Actor is not the official product of IAF CertSearch.

### Related Actors

- [Supplier Risk Monitor](https://apify.com/mikee368/supplier-risk-monitor): Combines register, VAT, sanctions and domain checks on a recurring supplier list.
- [EU Company Register: UK, FR, NO, FI, PL, CZ, SK, IE, RO, CH](https://apify.com/mikee368/eu-company-register-lookup): Checks that the certified organisation exists in an official register.

# Actor input Schema

## `supplierName` (type: `string`):

Certified organisation, as printed on the certificate. Required together with Country when an IAF API key is set.

## `certificateNumber` (type: `string`):

Issuer's certificate number. Example: FM 583920. A plausible shape is not proof the certificate is valid.

## `standard` (type: `string`):

Management-system standard. Example: ISO 9001, ISO 14001, ISO 27001, ISO 45001. A year suffix such as :2015 is accepted.

## `issuingBody` (type: `string`):

Certification body named on the certificate. Example: BSI, DNV, TÜV SÜD, Bureau Veritas, SGS, LRQA. Used to pick the manual checker and to compare an IAF record.

## `country` (type: `string`):

Country of the certified entity. ISO alpha-2 (GB) or a common name (United Kingdom). The IAF company call requires it. Accreditation registers are not queried from this field.

## `knownExpiryDate` (type: `string`):

Optional YYYY-MM-DD you already have. Stored for the expiry watch. It does not verify the certificate.

## `certificates` (type: `array`):

Batch. Each object has supplierName, certificateNumber, standard, issuingBody, and optional country and knownExpiryDate. Combined with the single fields above. At most 200 per run.

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

lookup returns one check per certificate and does not save watch state. watch saves state in the named store iso-cert-watch-state and, on a later run with the same Monitor ID, emits an alert when status changes or expiry enters the window.

## `monitorId` (type: `string`):

Separates saved watch state. Keys are seen-<monitorId> and meta-<monitorId> in the named store iso-cert-watch-state.

## `expiryWithinDays` (type: `integer`):

In watch mode, emit EXPIRY_SOON once per expiry date when a verified certificate expires within this many days.

## `iafApiKey` (type: `string`):

Your own IAF CertSearch Verification Users API key. Leave empty to skip the live call. The key is sent only as the x-http-authorization header and is not stored.

## `iafApiBaseUrl` (type: `string`):

HTTPS base URL from your IAF CertSearch API settings page. The prefill is the documented client host. Change it when your account page shows a different URL.

## `webhookUrl` (type: `string`):

Optional HTTPS URL. In watch mode, one JSON POST is sent when this run emits at least one alert. Lookup mode does not call it.

## `delayMs` (type: `integer`):

Pause after each IAF request before the next one. 0 to 5000. Default 200. No published per-minute cap; HTTP 429 stops further IAF calls for the run.

## Actor input object example

```json
{
  "supplierName": "Northwind Components",
  "certificateNumber": "FM 583920",
  "standard": "ISO 9001",
  "issuingBody": "BSI",
  "country": "GB",
  "knownExpiryDate": "",
  "certificates": [],
  "mode": "lookup",
  "monitorId": "default",
  "expiryWithinDays": 30,
  "iafApiBaseUrl": "https://api.iafcertsearch.org/api/client/v1",
  "webhookUrl": "",
  "delayMs": 200
}
```

# Actor output Schema

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

Verification rows. A cannot_verify_automatically row includes the official manual links.

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

Counts, IAF call count, and watch alerts. The API key is not stored.

# 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 = {
    "supplierName": "Northwind Components",
    "certificateNumber": "FM 583920",
    "standard": "ISO 9001",
    "issuingBody": "BSI",
    "country": "GB",
    "certificates": [],
    "monitorId": "default",
    "iafApiBaseUrl": "https://api.iafcertsearch.org/api/client/v1"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mikee368/iso-certificate-check").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 = {
    "supplierName": "Northwind Components",
    "certificateNumber": "FM 583920",
    "standard": "ISO 9001",
    "issuingBody": "BSI",
    "country": "GB",
    "certificates": [],
    "monitorId": "default",
    "iafApiBaseUrl": "https://api.iafcertsearch.org/api/client/v1",
}

# Run the Actor and wait for it to finish
run = client.actor("mikee368/iso-certificate-check").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 '{
  "supplierName": "Northwind Components",
  "certificateNumber": "FM 583920",
  "standard": "ISO 9001",
  "issuingBody": "BSI",
  "country": "GB",
  "certificates": [],
  "monitorId": "default",
  "iafApiBaseUrl": "https://api.iafcertsearch.org/api/client/v1"
}' |
apify call mikee368/iso-certificate-check --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mikee368/iso-certificate-check"
        }
    }
}
```

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/URQVbJN36clmGrv08/builds/Ih3tvAV1DdAqi4mmt/openapi.json
