# Company Dossier: Contact, VAT & Peppol (`olliolli/company-dossier-from-domain`) Actor

One row per company website: best contact route, company and VAT number checked in VIES, and optional Peppol e-invoicing registration and booking/quote routes. BE, NL, FR, LU, DE.

- **URL**: https://apify.com/olliolli/company-dossier-from-domain.md
- **Developed by:** [Tom J](https://apify.com/olliolli) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 company dossiers

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

## Company Dossier from a Domain (BE / NL / FR / LU / DE) — contact route, VAT and company number in one run

Give a list of company websites. Each site is read once, and you get one row per company with:

- the best **public contact route** (email, phone or contact form) with the page it came from;
- the **company number and VAT number** published on the site, checked with the official checksum rules and, optionally, with the European Commission's **VIES** service;
- optionally, whether that number is **registered for Peppol e-invoicing**, checked with the Peppol network's own lookup;
- optionally, **booking and quote-request routes** (online appointment, request a quote).

**Use it to:** enrich a lead or supplier list in one step, fill in VAT numbers for invoicing, check that a website names a valid company, or clean a list from a business-listing or Google Maps actor (pick its dataset as input).

This actor combines two engines that are also available on their own: the Contact Profile actor and the Company Legal ID Finder. One crawl is shared, so a dossier costs fewer page requests than running both.

### What you get per website

| Field | What it means |
|---|---|
| `status` | `complete` (contact and identity), `contact_only`, `identity_only`, `nothing_found`, `unreachable`, `redirected_to_other_domain`, `failed`, `invalid_input` |
| `contact` | Contact route of the Contact Profile engine: chosen email (role or named address on the company's own domain), phone, form, sources and evidence |
| `company_identity` | Country, main identifier, all identifiers of the site owner, VIES result, name match, confidence and reasons |
| `identifiers` / `third_party_identifiers` | Every number with its source page; numbers of hosting providers, agencies and payment providers are kept apart |
| `peppol` | Only when switched on: `registered` (with participant ID and the host name of the service provider's SMP server), `not_registered`, `source_unavailable` or `not_applicable` |
| `conversion_routes` | Only when switched on: proven booking/quote routes and candidate order/price routes, with evidence |

### How it works

- Reads only the company's own website: the same domain, robots.txt respected, at most 5 pages. Contact and legal-notice pages are read first. With `conversionRoutes` on, the page behind a booking or quote button is also opened (at most 8 per website, also when it is on a booking platform), through the same protected transport.
- No forms are sent, no logins, no mailbox checks; listing and review platforms are not scraped.
- VIES requests are spaced out and cached per run.
- Peppol: the identifiers found on the website are looked up in the Peppol SML, the central register of the e-invoicing network, through public DNS. No directory search, no account. At most 3 lookups per company. A busy or unavailable service is reported as `source_unavailable`, never as "invalid" or "not listed".

### Pricing

Pay per event (USD). Every run also carries Apify's standard start event (`apify-actor-start`): $0.00005 per run, or 5 cents per 1,000 runs.

| Event | When | Price |
|---|---|---|
| `website-checked` | every website that was checked (failures on our side are free) | $0.003 |
| `company-dossier` | an accepted contact route or a valid company identity was found | $0.03 |

- VIES and Peppol checks, the booking/quote routes and the contact options (`channelScope: all` for social profiles, WhatsApp and the booking link; `emailScope: all` for freemail and department addresses) are included.
- Rows are stored before they are charged; websites the run had no time for are left for a resumed run.
- Example: 1,000 websites with a dossier for 600 of them cost about $3 + $18 = $21.

### Limits — please read

- Everything is what the website itself publishes. An email or phone is a published route, not a verified mailbox or a consent to be contacted. Check the rules that apply to your outreach (for example GDPR and national direct-marketing rules).
- A number on a website is a claim of that website. VIES confirms that a VAT number is registered. It does not prove that the website belongs to that company; name match and confidence are signals, not proof.
- Peppol `not_registered` means no registration was found for the identifiers on the website. The company may receive invoices under another identifier.
- Many small businesses publish no company number on their website. Expect an identity for a minority of websites (see below).
- PDF documents are not read in this version.

### Measured results of the engines

The combined dossier has not been blind-tested as one product. The two engines were, separately:

- **Company identity** (October 2026, blind test on 400 unseen websites from BE, NL, FR, LU and DE): 103 websites gave an identity; for 99 of the 103 the main identifier was the right company's number, copied exactly with the right type. 108 of the 400 websites could not be reached.
- **Contact route** (preregistered comparison on 300 SME websites, October 2026): 152 companies got a correct address with all email categories, at 97.4% precision. A re-score of the same saved scans for the default categories gave 139 companies at 97.9% precision; that re-score is not a separate independent test.
- **Booking and quote routes** (blind test on 660 new SME websites in BE, NL and FR): 48 of 50 proven routes were correct. Order and price routes are returned as candidates and are not covered by that figure.

These are results on test samples, not a guarantee for your list. Try a sample of your own websites first.

### Data sources and terms

- The websites you provide, read according to their robots.txt.
- VIES, the VAT Information Exchange System of the European Commission (public service, no key needed).
- Peppol SML (Service Metadata Locator), the public DNS register of the Peppol network, run by OpenPeppol under `participant.sml.prod.tech.peppol.org` (only when `peppolCheck` is on).

# Actor input Schema

## `urls` (type: `array`):

Up to 500 company websites. Only each company's own site is read, once (robots.txt respected, at most 5 pages).

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

Optional. A dataset with a website column (for example the output of a business-listing or contact actor). Up to 5,000 rows / 500 unique websites are used. You are responsible for the origin of that list.

## `contactPurpose` (type: `string`):

Sales prefers sales, then general, never support; general addresses with nearby customer-service phrases are excluded. This is a conservative heuristic, not a guarantee of commercial suitability. General/support only accept their own mailbox category.

## `emailScope` (type: `string`):

role_person returns role and named company-domain addresses. all also includes freemail and department addresses; these categories are less validated.

## `channelScope` (type: `string`):

Extra social, booking and WhatsApp links are experimental and are not included by default.

## `viesCheck` (type: `boolean`):

Checks the found VAT number with the European Commission's official VIES service and compares the registered name with the names the website uses. Registered names/addresses are only shown when they contain a legal form.

## `peppolCheck` (type: `boolean`):

Checks whether the company number or VAT number found on the website is registered as a Peppol e-invoicing receiver, using the network's own lookup (SML, via DNS). No account needed. Only the identifiers found on the website are checked.

## `conversionRoutes` (type: `boolean`):

Also look for online booking/appointment and quote-request routes (proven, with evidence) and online ordering and price routes (candidates, with evidence). Opens the page behind each candidate button once (robots.txt respected) and adds a few seconds per website. Proven routes: in a blind test on 660 new SME websites (BE/NL/FR, October 2026), 48 of 50 booking and quote routes were correct. Candidates are not covered by that figure.

## `maxPages` (type: `integer`):

Includes the homepage. Contact and legal-notice pages are read first.

## `maxConcurrency` (type: `integer`):

Bounded parallel scans.

## `maxRunSeconds` (type: `integer`):

Stops new work at this time budget. Unprocessed URLs are listed in OUTPUT.

## Actor input object example

```json
{
  "urls": [
    "https://www.example.be/"
  ],
  "contactPurpose": "any",
  "emailScope": "role_person",
  "channelScope": "core",
  "viesCheck": true,
  "peppolCheck": false,
  "conversionRoutes": false,
  "maxPages": 5,
  "maxConcurrency": 5,
  "maxRunSeconds": 900
}
```

# Actor output Schema

## `results` (type: `string`):

One row per website: contact route, company identity with VIES, optional Peppol registration and booking/quote routes.

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

Counts by status, VIES and Peppol requests and unprocessed websites (OUTPUT).

# 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 = {
    "urls": [
        "https://www.example.be/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("olliolli/company-dossier-from-domain").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 = { "urls": ["https://www.example.be/"] }

# Run the Actor and wait for it to finish
run = client.actor("olliolli/company-dossier-from-domain").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 '{
  "urls": [
    "https://www.example.be/"
  ]
}' |
apify call olliolli/company-dossier-from-domain --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,olliolli/company-dossier-from-domain"
        }
    }
}
```

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/nI2Or2ZBZeyhQrS0b/builds/G9Ety01wx3gCXliRt/openapi.json
