# Tender Supplier Due Diligence (`mikee368/tender-supplier-due-diligence`) Actor

Build one supplier profile from a tender award: company register, VAT check and Impressum, with warning flags. Runs the lookups in one container.

- **URL**: https://apify.com/mikee368/tender-supplier-due-diligence.md
- **Developed by:** [Mike Berckmoes](https://apify.com/mikee368) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 supplier profiles

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Tender Supplier Due Diligence

One row per winning supplier. The row merges a company-register match, a VAT/TIN check and, when a website is known, the legal notice (Impressum, Mentions légales, legal notice). Warning flags call out a missing register match, a weak name, or a VAT number that does not exist.

### What does Tender Supplier Due Diligence do?

It starts from award notices or from a supplier list you already have.

- Paste suppliers (`name`, `country`, `vatNumber`, `registrationNumber`, `website`).
- Paste award notices, or give an Apify **dataset ID** from [eu-tender-monitor](https://apify.com/mikee368/eu-tender-monitor). Rows that already name a winner are used as they are. A TED award id such as `678111-2026` with no winner is read from the notice XML when the download is XML.
- Or turn on **Fetch recent TED awards**. TED search returns `winner-name` and `winner-identifier` (a French SIRET is reduced to its SIREN by the register lookup). The notice XML is only fetched when those fields are empty. The public TED website often answers the XML URL with an AWS WAF challenge instead of XML; the search fields avoid that.

For each supplier the Actor then:

1. Looks the company up in the same official registers as [EU Company Register Lookup](https://apify.com/mikee368/eu-company-register-lookup) (France, Norway, Finland, Poland, Czechia, Slovakia, Ireland, Romania, Switzerland, GLEIF, and the UK when you bring a Companies House key).
2. Checks a VAT or company number with the same rules as [EU TIN & VAT Validator](https://apify.com/mikee368/eu-tin-vat-validator). Personal numbers are format-checked only and are not sent to a register.
3. If a website is known, reads the legal notice with the same parser as [EU Impressum Lookup](https://apify.com/mikee368/eu-impressum-lookup). Person names are not stored.

### Why one run instead of calling the other Actors?

Calling each Actor with `Actor.call()` would start another container and, on the public Actors, add that Actor's own charge (a VAT check is $0.02 on its own). This Actor **imports those lookups and runs them in this container**. `RUN_SUMMARY.chain` is `in-process` and `childActorCalls` is 0. The other Actors are unchanged and are not billed.

A national register hit stops the chain before GLEIF, so a clear match does not pay for a second name search. Sitemap fetching is off unless you turn it on.

### What does a profile contain?

| Field | Meaning |
|---|---|
| `legalName`, `supplierName`, `supplierCountry` | Name from the award, and the register name when the match is strong |
| `noticeId`, `noticeSource`, `noticeTitle`, `buyerName`, `publicationDate` | The award the supplier came from |
| `registerSource`, `registrationNumber`, `registerStatus`, `lei` | Register row. Sources include `FR_SIRENE` and `GLEIF_LEI` |
| `vatNumber`, `vatValid`, `vatExistence`, `vatSource` | `vatValid` is true only when the official source says the number exists |
| `impressumStatus`, `impressumLegalName` | Legal-notice parse. Null when there was no website |
| `matchConfidence` | 0–100 |
| `warnings` | See below |

Warnings:

| Flag | Meaning |
|---|---|
| `no_register_match` | No register row survived the name filter |
| `name_mismatch` | A row was kept but the name scores under 75 |
| `register_inactive` | The matched company is not active |
| `vat_invalid` | The number fails the check digit or the source says it does not exist |
| `vat_unverified` | No number, or the source was down |
| `vat_personal` | A personal number was format-checked only |
| `no_website` | Impressum was requested and no website was known |
| `impressum_not_found` | The site had no usable legal notice |
| `impressum_mismatch` | The legal-notice name does not match the supplier or the register |
| `impressum_fetch_error`, `impressum_robots` | The site or robots.txt blocked the fetch |

### How to run it

1. Paste suppliers, or a dataset id, or turn on **Fetch recent TED awards**.
2. Leave **Max supplier profiles** at 1 while you try it. The maximum is 25.
3. A Companies House key is optional and only used for UK companies.
4. Open the dataset. `RUN_SUMMARY` in the key-value store has the warning counts.

A run with no suppliers, no dataset and **Fetch recent TED awards** left off finishes without charging and without calling TED.

### How much does a profile cost?

**Pay per event: $0.01 per `supplier-profile`.**

That is one merged profile, including profiles that carry warning flags, because the lookups ran. Invalid input and notices with no winner are not charged. Platform compute is separate. The default run is 256 MB.

Do not add the child Actor prices on top. Those Actors are not called.

### Sources

TED award notices are reused under Commission Decision 2011/833/EU. Register, VIES and legal-notice terms are the same ones the existing Actors already use. No new data source was added.

Official registers can name a sole trader. This Actor drops sole traders by default. Do not use it to build a file of private individuals.

# Actor input Schema

## `datasetId` (type: `string`):

Optional Apify dataset of eu-tender-monitor rows or award notices. Award rows without a winner name are read from the TED XML when the notice id looks like 123456-2026.

## `notices` (type: `array`):

Optional notice objects. A winner can sit on winnerName, supplierName, or winners\[]. Example: {"source":"TED","noticeId":"1-2026","noticeType":"can-standard","winnerName":"Example SAS","winnerCountry":"FR"}.

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

Optional suppliers to check directly. Each object can include name, country (ISO2 or ISO3), registrationNumber, vatNumber and website.

## `fetchAwards` (type: `boolean`):

When on, and the supplier list is still short, search TED for recent can-standard notices and read the winner from the notice XML. Off by default so a blank run stays free.

## `fetchWinnerDetails` (type: `boolean`):

When an award notice has a TED publication number but no winner name, download that notice XML and read the contractor.

## `dateFrom` (type: `string`):

YYYY-MM-DD. Used only when Fetch recent TED awards is on. Empty means the last 14 days.

## `countries` (type: `array`):

ISO countries for the TED award search (alpha-2 or alpha-3). Default FRA, which has a free company register.

## `maxSuppliers` (type: `integer`):

Stop after this many suppliers. Maximum 25, so a run stays small.

## `maxDatasetItems` (type: `integer`):

How many rows to read from datasetId. Maximum 500.

## `includeImpressum` (type: `boolean`):

When a website is known, read the Impressum / Mentions légales page. Person names are never stored.

## `tryCommonPaths` (type: `boolean`):

If the homepage has no legal-notice link, try /impressum, /mentions-legales and the other common paths.

## `useSitemap` (type: `boolean`):

Off by default. Turn on only when the legal page is missing from the homepage. It adds extra requests.

## `matchMode` (type: `string`):

How close a register name must be. strict keeps whole-word matches and better. balanced and loose keep weaker names and raise name_mismatch.

## `companiesHouseApiKey` (type: `string`):

Free key from developer.company-information.service.gov.uk. Without it the UK register is skipped. GLEIF is still queried.

## Actor input object example

```json
{
  "datasetId": "",
  "notices": [],
  "suppliers": [
    {
      "name": "Example SAS",
      "country": "FR",
      "website": "https://example.fr"
    }
  ],
  "fetchAwards": false,
  "fetchWinnerDetails": true,
  "dateFrom": "",
  "countries": [
    "FRA"
  ],
  "maxSuppliers": 1,
  "maxDatasetItems": 100,
  "includeImpressum": true,
  "tryCommonPaths": true,
  "useSitemap": false,
  "matchMode": "strict"
}
```

# Actor output Schema

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

Dataset with one row per supplier

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

Counts, warning flags and the in-process chain note

# 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 = {
    "suppliers": [
        {
            "name": "Example SAS",
            "country": "FR",
            "website": "https://example.fr"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mikee368/tender-supplier-due-diligence").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 = { "suppliers": [{
            "name": "Example SAS",
            "country": "FR",
            "website": "https://example.fr",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("mikee368/tender-supplier-due-diligence").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 '{
  "suppliers": [
    {
      "name": "Example SAS",
      "country": "FR",
      "website": "https://example.fr"
    }
  ]
}' |
apify call mikee368/tender-supplier-due-diligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mikee368/tender-supplier-due-diligence"
        }
    }
}
```

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/qTYC529T11TjzA2T2/builds/kWaAg5tcwIFfunvQN/openapi.json
