# Peppol & VAT Check for Client Lists (`olliolli/peppol-client-data-check`) Actor

Check your client list for e-invoicing: VAT format and check digits, VIES status, name match and Peppol registration for BE, NL, LU, FR and DE. Printable report in Dutch, French or English.

- **URL**: https://apify.com/olliolli/peppol-client-data-check.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 $10.00 / 1,000 client checks

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

Check a **client list for Peppol e-invoicing and VAT**: VAT format and check digits, **VIES status**, name and postal code match, and **Peppol registration** for Belgium, the Netherlands, Luxembourg, France and Germany. Get a printable report in Dutch, French or English with what is OK and what to fix. Built for the Belgian B2B e-invoicing obligation (Peppol, mandatory since 1 January 2026). **$10 per 1,000 clients checked.**

### What you can do with it

- **Prepare for Belgian e-invoicing**: see which clients are on Peppol and which are not.
- **Clean your client database**: invalid VAT formats, wrong check digits, inactive VAT numbers, name and postal code mismatches, duplicates.
- **Track changes**: compare with an earlier run (newly registered on Peppol, registration removed, VAT became (in)active).
- **Hand your accountant a report**: printable HTML and a CSV that opens cleanly in Excel.

### How to check your client list in 3 steps

1. **Upload your clients** in `clients`, `identifiers` or a dataset (column names like `btw`, `vat_number`, `ondernemingsnummer`, `tva`, `siren`, `ust_id` are recognised).
2. **Choose the report language** (Dutch by default, or French or English).
3. **Click Start.** You get one row per client, a summary and a one-page report.

### Input

| Field | Meaning | Default |
|---|---|---|
| `clients` | Up to 2,000 `{id, name?, postalCode?, reference?, country?, peppolId?}` | — |
| `identifiers` | Plain list of numbers | — |
| `datasetId` | Dataset (up to 5,000 rows read, 2,000 clients checked) | — |
| `defaultCountry` | Country for numbers without a prefix | `BE` |
| `viesCheck` / `peppolCheck` | Sources to check (at least one on) | both on |
| `previousDatasetId` | Earlier run, to report changes | — |
| `reportLanguage` | `nl`, `fr`, `en` | `nl` |
| `maxRunSeconds` | Run time budget | 1,800 |

### Output

- **Dataset**: one row per client with normalised number, `vies`, `peppol`, `findings`, `overall` (`ok`, `attention`, `error`, `incomplete`), `complete` and `changes`.
- **REPORT_HTML**: printable report sorted by severity.
- **REPORT_CSV**: semicolon-separated, UTF-8 with BOM, leading zeros kept.
- **OUTPUT**: totals (OK / attention / error, Peppol registered or not, VIES valid or not, duplicates, changes).

Peppol is checked per country under its scheme: Belgium `0208` (enterprise number) and `9925` (VAT), Netherlands `9944`, Luxembourg `9938`, Germany `9930`, France `0002`. In Belgium, active in VIES but not on Peppol is an `error`.

### How much does a Peppol and VAT check cost?

| Event | Price |
|---|---|
| Client checked (unique number, fully checked) | $0.01 |
| Actor start | $0.00005 |

**Example:** 500 clients, of which 480 unique and valid = about **$4.80**.

#### What you are not charged for

Duplicates, numbers without a recognisable format or with wrong check digits, partly checked clients (listed for a recheck) and clients where VIES and Peppol were both unreachable.

### Privacy

You are the controller of the client list you upload; the Actor only processes it to produce your report. Data goes only to the European Commission's VIES service (the VAT number) and public DNS (a hash of the Peppol identifier). Registered names and addresses are shown only for companies with a legal form, never for natural persons. Not legal or tax advice.

### Good to know

- Other EU countries: VIES only (no check digits, no Peppol).
- VIES unreachable is reported as `vies_unavailable`, never as "invalid".
- Not yet measured on a real client list; checksum, VIES, DNS and report logic are covered by offline tests.

### FAQ

**Is Peppol e-invoicing mandatory in Belgium?** For B2B invoices between Belgian VAT-registered companies, yes, since 1 January 2026.

**What does "not registered on Peppol" mean?** No receiver registration was found for that client's numbers; they may use another identifier.

**Can I check Dutch, French and German clients too?** Yes: BE, NL, LU, FR and DE get full checks; other EU countries VIES only.

**Can I compare with last month's check?** Yes, pass the earlier dataset in `previousDatasetId`.

### Need help?

Open an issue on the **Issues** tab.

# Actor input Schema

## `clients` (type: `array`):

Up to 2,000 clients. Each client: {"id": VAT or company number in any common format (required), "name": optional, "postalCode": optional, "reference": optional, for example your client number, "country": optional, BE / NL / FR / LU / DE, used for numbers without a country prefix, "peppolId": optional, the Peppol ID you have on file for this client, e.g. 0208:0403170701 or 0204:991-12345-67}. With a name and postal code the actor also compares them with the VIES record. Entries without an id are skipped.

## `identifiers` (type: `array`):

VAT or company numbers, one per line, in any common format (BE 0403.170.701, 0403170701, NL004495445B01, FR60513067645, LU26375245, DE136695976). Use this or clients; both are combined.

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

A dataset with one row per client. Column names are matched loosely (capitalisation, spaces, dashes and accents do not matter): vat, vat_number, vat_nr, btw, btw_nummer, btw_nr, tva, numero_tva, ust_id, ust_idnr, enterprise_number, ondernemingsnummer, kbo, bce, company_number, siren or siret for the number (siren/siret are read as French and ust_id as German when the number has no prefix); name, company, naam or nom; postal_code, postcode, zip, code_postal or plz; reference, ref, client_id or klantnummer; optionally country / land / pays and peppol_id / peppol (a Peppol ID like 0208:0403170701). Up to 5,000 rows are read; at most 2,000 clients are processed per run.

## `defaultCountry` (type: `string`):

Used when a number has no country prefix and its shape does not show the country. A 10-digit number starting with 0 or 1 is always read as a Belgian enterprise number; a 9-digit number is read as a SIREN only when this is France.

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

Checks whether the client is registered as a receiver in the Peppol network, using the network's own lookup (SML, via public DNS). No account needed.

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

Checks with the European Commission's VIES service whether the VAT number is active, and compares your name and postal code with the VIES record. Switch on VIES, Peppol or both.

## `previousDatasetId` (type: `string`):

Optional. The dataset of an earlier run of this actor. Rows get a changes list: newly registered on Peppol, Peppol registration removed, VAT became (in)active, other Peppol provider, new in the list.

## `reportLanguage` (type: `string`):

Language of the HTML and CSV reports (key-value store records REPORT_HTML and REPORT_CSV).

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

Stops new work at this time budget. Unprocessed clients are listed in OUTPUT and are picked up when the run is resumed.

## Actor input object example

```json
{
  "clients": [
    {
      "id": "BE 0202.239.951",
      "name": "Proximus",
      "postalCode": "1030",
      "reference": "K-0001"
    }
  ],
  "defaultCountry": "BE",
  "peppolCheck": true,
  "viesCheck": true,
  "reportLanguage": "nl",
  "maxRunSeconds": 1800
}
```

# Actor output Schema

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

One row per client: normalised number, VIES, Peppol, findings and overall status.

## `report` (type: `string`):

One-page report with the counts and a table sorted by severity.

## `csv` (type: `string`):

One line per client, semicolon-separated, UTF-8 with BOM.

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

Totals, clients to recheck and clients that were not processed (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 = {
    "clients": [
        {
            "id": "BE 0202.239.951",
            "name": "Proximus",
            "postalCode": "1030",
            "reference": "K-0001"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("olliolli/peppol-client-data-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 = { "clients": [{
            "id": "BE 0202.239.951",
            "name": "Proximus",
            "postalCode": "1030",
            "reference": "K-0001",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("olliolli/peppol-client-data-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 '{
  "clients": [
    {
      "id": "BE 0202.239.951",
      "name": "Proximus",
      "postalCode": "1030",
      "reference": "K-0001"
    }
  ]
}' |
apify call olliolli/peppol-client-data-check --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,olliolli/peppol-client-data-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/XZ3Ot8sjyUEZTrYeO/builds/0h0L6aPldS7MgWw02/openapi.json
