# EU VAT number validator + French company lookup (VIES, SIREN) (`garonne/eu-vat-siren-check`) Actor

Validates EU VAT numbers against the official VIES registry and enriches French SIREN/SIRET identifiers via the official INSEE-backed recherche-entreprises API. Pay-per-event: one company-record charge per successfully verified identifier.

- **URL**: https://apify.com/garonne/eu-vat-siren-check.md
- **Developed by:** [matheo daney](https://apify.com/garonne) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 company records

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## EU VAT (VIES) validator + French SIREN enrichment — invoice-ready company records

**Validate any EU VAT number against the official VIES registry and turn a French SIREN or SIRET into a complete, invoice-ready company record** — legal name, registered address, NAF/APE activity code, active/closed status, and VAT validity, all sourced live from official government registries. Built for AI agents and automated invoicing pipelines that need a single, trustworthy answer per identifier, not a scraped guess.

### Use this Actor when…

- An AI agent is about to **generate or accept an invoice** and needs to confirm the counterparty's VAT number is real before the document goes out.
- You're building a **KYB (Know Your Business) or supplier onboarding check** and need official legal name + address + activity code for a list of SIREN/SIRET numbers, not just a validity flag.
- You have a **French SIREN or SIRET** and need its EU VAT number (and whether that VAT number is currently valid), or the reverse: you have an EU VAT number and want the underlying French company record.
- You need this as a **tool an AI agent can call directly** — via the Apify API, the [Apify MCP server](https://docs.apify.com/integrations/mcp), or an [n8n](https://docs.apify.com/platform/integrations/n8n) workflow — and get back one flat JSON object per identifier, ready to drop into a downstream decision (accept the invoice / reject it / flag for review).

This Actor does **not** scrape anything. Every field comes from two official, public, no-API-key government sources: the European Commission's VIES registry and France's `recherche-entreprises.api.gouv.fr` (INSEE/INPI-backed). See **Limits & data sources** below for exact citations.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `identifiers` | array of strings | *(required)* | One identifier per array item. Two formats, auto-detected: an EU VAT number with country prefix (e.g. `"FR12345678901"`, `"DE123456789"`), or a bare French `SIREN` (9 digits) / `SIRET` (14 digits) with no prefix. |
| `includeFrenchEnrichment` | boolean | `true` | Controls the *second*, cross-referencing lookup only possible for French identifiers (VAT → company record, or SIREN/SIRET → VAT validity). See the field's in-console description for the exact behavior in each direction. |
| `maxConcurrency` | integer | `2` | How many identifiers to verify in parallel (1–5). Kept conservative on purpose — see **Limits & data sources**. |

Full field-level descriptions (written for both humans and LLM agents reading the input schema) are in [`.actor/input_schema.json`](.actor/input_schema.json).

#### Example input

```json
{
  "identifiers": ["FR39356000000", "552049447", "DE123456789"],
  "includeFrenchEnrichment": true,
  "maxConcurrency": 2
}
```

### Output example

Real output from a local test run (2026-09-03) — a French VAT number resolved and fully enriched:

```json
{
  "input": "FR39356000000",
  "type": "vat",
  "countryCode": "FR",
  "vatNumber": "39356000000",
  "vatValid": true,
  "vatValidationSource": "VIES",
  "dgfipVatMatch": true,
  "vatCheckedAt": "2026-09-04T00:10:15.952Z",
  "legalName": "LA POSTE",
  "address": "DIRECTION GENERALE DE LA POSTE 9 RUE DU COLONEL PIERRE AVIA 75015 PARIS",
  "siren": "356000000",
  "siret": "35600000000048",
  "naf": "53.10Z",
  "activeStatus": "active",
  "sources": [
    "https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number",
    "https://recherche-entreprises.api.gouv.fr/search"
  ],
  "charged": true,
  "error": null
}
```

And a bare French SIREN, resolved and cross-checked for VAT validity in the other direction:

```json
{
  "input": "531680445",
  "type": "siren",
  "countryCode": "FR",
  "vatNumber": "22531680445",
  "vatValid": true,
  "vatValidationSource": "VIES",
  "dgfipVatMatch": true,
  "vatCheckedAt": "2026-09-04T00:11:57.532Z",
  "legalName": "TOTALENERGIES MARKETING FRANCE",
  "address": "562 AVENUE DU PARC DE L'ILE 92000 NANTERRE",
  "siren": "531680445",
  "siret": "53168044500024",
  "naf": "46.71Z",
  "activeStatus": "active",
  "sources": [
    "https://recherche-entreprises.api.gouv.fr/search",
    "https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number"
  ],
  "charged": true,
  "error": null
}
```

And a real DGFiP fallback in action (2026-09-04, same live run as the two examples above): VIES genuinely timed out on this French VAT number after its full retry budget — this Actor still delivered a charged, sourced, correctly-labeled record instead of an uncharged failure. Note `vatValid: null` — the fallback never claims a VIES confirmation it doesn't have:

```json
{
  "input": "FR35552049447",
  "type": "vat",
  "countryCode": "FR",
  "vatNumber": "35552049447",
  "vatValid": null,
  "vatValidationSource": "DGFIP_FALLBACK",
  "dgfipVatMatch": true,
  "vatCheckedAt": null,
  "legalName": "SOCIETE NATIONALE SNCF (SNCF)",
  "address": "2 PLACE AUX ETOILES 93210 SAINT-DENIS",
  "siren": "552049447",
  "siret": "55204944776279",
  "naf": "49.10Z",
  "activeStatus": "active",
  "sources": [
    "https://recherche-entreprises.api.gouv.fr/search"
  ],
  "charged": true,
  "error": null
}
```

Every dataset item has exactly this set of keys, whether verification succeeded or failed — a `null` value means "not applicable to this identifier / this lookup wasn't attempted", never "unknown due to an error" (that's what the `error` field is for).

| Field | Meaning |
|---|---|
| `input` | The raw identifier as given. |
| `type` | `"vat"`, `"siren"`, `"siret"`, or `"malformed"`. |
| `countryCode` | 2-letter VIES country code (`"FR"` for SIREN/SIRET-derived records). |
| `vatNumber` | The VAT number checked (without country prefix), if any. |
| `vatValid` | `true`/`false` **from an actual VIES response**, or `null` if VIES never answered — never `true` from any other source, see **Limits & data sources**. |
| `vatValidationSource` | `"VIES"` (an actual VIES response backs `vatValid`), `"DGFIP_FALLBACK"` (VIES never answered; `dgfipVatMatch` is the positive signal instead), or `null`. |
| `dgfipVatMatch` | `true`/`false` if the DGFiP-sourced list for this SIREN was checked, `null` if it wasn't (non-French, or the lookup itself failed). Independent of whether VIES also answered. |
| `vatCheckedAt` | ISO 8601 timestamp of the VIES response, or `null` (including when `vatValidationSource` is `"DGFIP_FALLBACK"` — VIES was never actually reached). |
| `legalName` | Official registered company name. |
| `address` | Official registered address. |
| `siren` | French SIREN (9 digits), if resolved. |
| `siret` | French SIRET (14 digits) of the relevant establishment, if resolved. |
| `naf` | French NAF/APE activity code, if resolved. |
| `activeStatus` | `"active"`, `"closed"`, `"not_found"`, or `null`. |
| `sources` | Array of the official API URL(s) actually consulted for this record. |
| `charged` | `true` if this record triggered a `company-record` charge. |
| `error` | Explanation if verification failed (implies `charged: false`), otherwise `null`. |

You can download the dataset in JSON, CSV, Excel, or several other formats from the Apify Console or the API. The Console's *Output* tab shows a pre-labeled table view ([`.actor/dataset_schema.json`](.actor/dataset_schema.json)) with the most decision-relevant columns (`charged`, `vatValid`, `activeStatus`, `legalName`) up front.

### Pricing

This Actor uses Apify's **pay-per-event (PPE)** pricing model — see [Apify's PPE documentation](https://docs.apify.com/sdk/js/docs/concepts/pay-per-event) for how the mechanism itself works. There is a single billable event:

- **`company-record`** — charged once per identifier for which verification **genuinely completed**: VIES (or, for a French SIREN/SIRET, `recherche-entreprises.api.gouv.fr`) returned a definitive answer, whether that answer is "valid" / "found" or a confirmed "invalid" / "not found". A confirmed negative is still a usable decision for an invoicing pipeline, so it is charged the same as a confirmed positive.

**A French VAT number recovered through the DGFiP fallback (2026-09-04) is also charged**, on the same event, at the same price — deliberately, not by default/oversight. Rationale: it delivers real, sourced, useful company data (legal name, address, NAF, status, and confirmation the VAT number is on DGFiP's own active list) exactly like any other successful record, and this Actor already charges the same for a *confirmed-invalid* VIES answer on the reasoning that a definitive negative is itself billable value — a DGFiP-confirmed positive meets that same bar. The precondition is that the output is **never ambiguous** about which source backs the answer (`vatValidationSource`, see **Output** above and **Limits & data sources** below) — a client who specifically needs VIES-grade proof can filter on `vatValidationSource === "VIES"` and treat anything else as unconfirmed, at no extra integration cost. This is revisited if real usage shows this reads as a surprise rather than a documented trade-off (see input schema and the section below, both written before a client would ever hit this case).

**You are never charged for:**

- a malformed identifier (rejected before any API call is made);
- a run where the upstream registry could not be reached even after automatic retries with backoff, **and** (for a French VAT number) the DGFiP fallback could not confirm the number either.

Target price: **$0.008 per `company-record`** ($8 per 1,000 records) — configured on the Actor's *Pricing* page in Apify Console, not in this code. In the code, the event is declared with `Actor.charge()` / the `Actor.pushData(item, 'company-record')` shortcut (see [`src/main.js`](src/main.js) and [`src/record.js`](src/record.js)).

### Limits & data sources

**VIES (VAT validation)** — European Commission, `POST https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number`. Official Swagger/OpenAPI spec: `https://ec.europa.eu/assets/taxud/vow-information/swagger_publicVAT.yaml` (fetched and cross-checked 2026-09-03/04). No API key. A sandbox exists at `check-vat-test-service` (country `DE`/etc. + VAT number `"100"` → always valid, `"200"` → always invalid) — verified live 2026-09-03; this Actor deliberately never calls it, since a production invoicing tool has no reason to return sandbox data.

VIES has no published fixed rate limit, but throttles under load both **per member state** (`MS_MAX_CONCURRENT_REQ`) and **globally** (`GLOBAL_MAX_CONCURRENT_REQ`). Per the European Commission's own VIES "Technical information" page (`https://ec.europa.eu/taxation_customs/vies/#/technical-information`, cross-checked 2026-09-04 — see [`src/vies.js`](src/vies.js) header for exactly how, since that page cannot be fetched/rendered by this tooling directly), those two codes plus `MS_UNAVAILABLE`, `SERVICE_UNAVAILABLE` and `TIMEOUT` are all **transient/load-related** and worth retrying; `INVALID_INPUT`, `INVALID_REQUESTER_INFO`, `VAT_BLOCKED` and `IP_BLOCKED` are **definitive** and never retried.

⚠️ **On the Apify platform, this matters more than it would from a personal machine**: outbound IPs are shared across many concurrent customers' Actor runs, so VIES's per-member-state quota can be consumed by traffic that has nothing to do with this Actor's own request rate — confirmed by a real production failure (`MS_MAX_CONCURRENT_REQ` on a French VAT number that passes reliably from a personal IP, build 0.1.1, 2026-09-03). Two mitigations, both in [`src/vies.js`](src/vies.js):

- **Patient retry with jitter**: up to 6 retries (7 attempts) per identifier, exponential backoff (3s, 6s, 12s, 24s, 30s, capped at 30s) multiplied by a wide random jitter factor (0.5×–1.6×) — the jitter matters specifically *because* the IP is shared: without it, many Apify containers hitting the same transient fault at the same instant would retry in lockstep and re-collide. A hard **90-second total time budget per identifier** bounds how long one contested identifier can hold up a batch, regardless of attempts remaining.
- **Per-country serialization**: VIES's concurrency limit is per member state, so this Actor never has two of its own requests for the *same* country in flight at once (that would only add to its own contribution to that country's queue, for no benefit) — while requests for *different* countries, and all `recherche-entreprises` calls, still run fully in parallel.

**A transient failure (retries exhausted) is never confused with a genuinely invalid identifier, nor with a DGFiP-fallback record — three different `vatValid`/`charged` combinations, never overlapping:**

- Confirmed by VIES, either way: `vatValid: true` or `false`, `vatValidationSource: "VIES"`, `charged: true`, `error: null` — a real, usable, billable answer either way.
- Recovered via the DGFiP fallback (French VAT numbers only, see below): `vatValid: null` (never `true`/`false` from this source), `vatValidationSource: "DGFIP_FALLBACK"`, `dgfipVatMatch: true`, `charged: true`, `error: null`.
- Genuine transient failure (neither source could confirm anything): `vatValid: null`, `vatValidationSource: null`, `charged: false`, `error` reads exactly `"VIES temporarily unavailable after N attempt(s) (last fault: <code>): retry later."` — a client can branch on all of this without reading logs (retry later / treat as a confirmed rejection / treat as DGFiP-sourced but not VIES-grade). A malformed identifier (never sent to VIES at all) gets its own, differently-worded `error` instead.

**End-of-run retry pass (2026-09-04)**: VIES contention is usually momentary. Any identifier that is still a transient failure after all of the above is held back rather than pushed as final — after the whole batch finishes, this Actor waits **60 seconds** (contention often clears once other identifiers have stopped competing for the same member state) and retries exactly that subset once more, together. Whatever that second pass produces (success, a confirmed-invalid answer, or still a transient failure) is what actually gets pushed and, if applicable, charged — never both a failed and a successful record for the same identifier.

*Total run time budget* (documented, not enforced by an extra hard timeout beyond the 90s-per-identifier one above): the worst case is bounded by how many identifiers share the most-contested country code, since same-country VIES calls always serialize. For K identifiers sharing that country: **phase 1 ≈ K × 90s, + a fixed 60s wait, + phase 2 ≈ K × 90s again if still contested.** For example, 2 same-country VAT numbers both genuinely contested (observed for real in this Actor's own testing) → up to ≈ 420s (7 minutes) for that pair, run alongside the rest of the batch rather than blocking it.

**Apify Proxy was investigated (2026-09-04) and deliberately NOT used.** The hypothesis was that Apify's shared outbound IPs cause `MS_MAX_CONCURRENT_REQ` by mixing this Actor's traffic with other customers' runs at the IP level. The best evidence found — cross-checked across a specialist third-party VIES provider and independent developer reports, no official EC statement found either way (see the full citation trail in the project's session history) — is that this VIES limit is a **global counter per member state, not scoped to the caller's IP address**: it reflects total worldwide demand on that member state's backend, not "how many Apify users happen to share an IP with us" at that moment. If that is correct, routing through Apify Proxy (residential or datacenter) would not fix this specific failure mode — datacenter proxy groups are, per Apify's own documentation, *also* a pool "shared across multiple customers" unless paid dedicated groups are purchased (custom, unpublished pricing), so it would not even change the sharing situation in the way the hypothesis assumed. The end-of-run retry pass above is the mitigation actually implemented instead.

**recherche-entreprises.api.gouv.fr (French company data)** — maintained by DINUM (Direction interministérielle du numérique), backed by INSEE/SIRENE and INPI. `GET https://recherche-entreprises.api.gouv.fr/search?q=<9 or 14 digits>`. No API key. **Documented hard limit: 7 requests/second per IP, 30/second per ASN** (source: the API's own OpenAPI description, fetched 2026-09-03 — see `https://recherche-entreprises.api.gouv.fr/openapi.json`); exceeding it returns HTTP 429 with a `Retry-After` header, which this Actor honors.  Licence: **Licence Ouverte 2.0 / Open Licence 2.0 (Etalab)**.

`maxConcurrency` defaults to **`2`** (lowered from `3` after the 2026-09-04 resilience fix above) and is capped at `5`, and is never allowed to exceed the number of identifiers in the batch. It bounds SIRENE parallelism directly, and VIES parallelism *across different countries* — same-country VIES calls are always serialized regardless of this setting (see above).

#### French DGFiP fallback (2026-09-04) — and why it is NOT the same thing as a VIES check

For a **French VAT number specifically**, if VIES is still a transient failure after everything above (retries, per-country serialization, the end-of-run retry pass), this Actor makes one more attempt: it checks whether that number appears in `recherche-entreprises`'s `tva` field for the corresponding SIREN — a direct pass-through of a dataset DGFiP itself publishes daily.

**What that field actually is** (traced to source, 2026-09-04): the API's own OpenAPI description labels it *"Liste des numéros de TVA intracommunautaire français actifs (source : DGFiP)"*. Following the data pipeline that populates it — `annuaire-entreprises-data-gouv-fr/search-infra`, `workflows/data_pipelines/tva/` (`github.com/annuaire-entreprises-data-gouv-fr/search-infra`, read 2026-09-04) — it is a **daily, unmodified pass-through** of a CSV published at `https://data.economie.gouv.fr/explore/assets/numeros-de-tva-intracommunautaires-francais/`. That dataset's own catalog metadata (`data.economie.gouv.fr` OpenDataSoft API, `/api/v2/catalog/datasets/numeros-de-tva-intracommunautaires-francais`, fetched 2026-09-04) states: **Publisher: DGFIP**, description *"Population des numéros de TVA intracommunautaire français actifs"*, refreshed daily. **This is real, sourced, government-published data — not a check-digit computed on the fly for an arbitrary SIREN** (the check-digit formula is documented separately in [`src/identifier.js`](src/identifier.js), used only to *derive which number to look up*, never to fabricate a "valid" answer).

**What it is not, and why VIES still matters**: EU law makes VIES specifically — not a national tax authority's own records — the reference for the buyer's VAT ID when a seller applies the intra-Community VAT exemption. Directive 2006/112/EC ("the VAT Directive"), Article 138, as amended by Council Directive (EU) 2018/1910 (in force since **2020-01-01**, the EU's "2020 Quick Fixes"), made the buyer's VAT identification number being valid **specifically in VIES** a *substantive* condition for that exemption — not a mere formality a seller can cure after the fact. A DGFiP daily extract, however faithfully sourced, is a different instrument: it does not carry that same legal weight for zero-rating an intra-EU supply, and it reflects DGFiP's own daily-refreshed view, not VIES's real-time cross-border-harmonized one (a lag of up to ~24h is possible between the two).

**Consequently**: `vatValid` is *never* set to anything but `null` by this fallback — only an actual VIES response ever sets it. The fallback's positive signal lives entirely in `dgfipVatMatch: true` + `vatValidationSource: "DGFIP_FALLBACK"`, so a client cannot mistake this for a VIES-confirmed record by reading `vatValid` alone, and can trivially filter it out (`vatValidationSource === "VIES"`) if a VIES-grade proof is what they actually need. The fallback only ever produces a *positive* result — a SIREN found but with no matching entry in the DGFiP list is left as an ordinary, uncharged transient failure (that absence is too weak a signal, on its own, to report as a confirmed rejection).

**Update frequency**: both registries are updated by their respective national administrations on their own schedule (typically daily for SIRENE); `vatCheckedAt` always reflects the exact moment of the check, not a cached value — this Actor never caches results between runs.

### Legal / GDPR

This Actor returns **company data only** — legal name, registered address, activity code, administrative status, VAT number. **It never returns personal data about natural persons** (no director/manager/beneficial-owner names, no personal contact details), even though the underlying `recherche-entreprises.api.gouv.fr` API can expose executive/officer names (`dirigeants`) via other query parameters. This Actor does not request, read, or forward that field.

Both source APIs are public, unauthenticated, official government registries designed for exactly this kind of automated lookup; no data is scraped, and no personal data is stored by this Actor beyond the lifetime of the run's dataset (which you control and can delete like any other Apify dataset).

### Integrations (API, MCP, n8n)

- **Apify API** — run this Actor from any HTTP client via `POST https://api.apify.com/v2/acts/<your-username>~eu-vat-siren-check/run-sync-get-dataset-items` (or the async run + get-dataset-items pair) with your API token. See the Actor's *API* tab in Apify Console for ready-to-copy snippets (cURL, Python, JS).
- **MCP (Model Context Protocol)** — expose this Actor as a tool to any MCP-compatible AI agent via the [Apify MCP server](https://docs.apify.com/integrations/mcp) (`https://mcp.apify.com`, or `@apify/actors-mcp-server` for local/self-hosted use). Once discovered, an agent can call it directly with a natural-language list of identifiers.
- **n8n** — use the official [Apify node for n8n](https://docs.apify.com/platform/integrations/n8n) (`@apify/n8n-nodes-apify`) to run this Actor as a step in a workflow — e.g. triggered right before an "Invoice" node, feeding it `vatValid`/`vatValidationSource`/`activeStatus` to gate the send (check `vatValidationSource === "VIES"` specifically if the workflow needs VIES-grade proof, not just any sourced positive answer).

# Actor input Schema

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

List of company identifiers to verify, one per array item. Two formats are accepted, auto-detected per item: (1) an EU VAT number with its 2-letter country prefix, e.g. "FR12345678901", "DE123456789" (spaces, dots and dashes are stripped automatically) — validated live against the official VIES registry; (2) a French company number with no prefix: a 9-digit SIREN (e.g. "552049447") or a 14-digit SIRET (e.g. "55204944700010") — looked up on the French national business register. Any item that matches neither shape is returned as an error record (no charge). Do not mix multiple identifiers in one string; one identifier per array item.

## `includeFrenchEnrichment` (type: `boolean`):

Controls the SECOND, cross-referencing lookup that is only possible for French identifiers; the FIRST lookup native to whatever format you supplied always runs regardless of this setting. If true (default): a French VAT number (FRxxxxxxxxxxx) also gets its legal name, registered address, NAF/APE code and active/closed status pulled from recherche-entreprises.api.gouv.fr (using the SIREN embedded in the VAT number); a bare SIREN/SIRET also gets its French VAT number derived and validated live against VIES, populating vatValid. If false: a French VAT number is checked for VIES validity only (legalName/address/naf/siren stay null); a bare SIREN/SIRET is checked against recherche-entreprises only (vatValid stays null, no VIES call is made). Non-French VAT numbers are never affected by this setting — they are always VIES-checked only, since no French company registry applies to them. NOT related to the DGFiP fallback (see README 'Limits & data sources'): if VIES is unreachable for a French VAT number, this Actor always attempts the recherche-entreprises fallback regardless of this setting, since that is a resilience mechanism, not optional enrichment — disabling this flag never disables it.

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

How many identifiers to verify in parallel (never more than the number of identifiers you supplied, regardless of this value). Keep this conservative: the French recherche-entreprises API enforces a hard limit of 7 requests/second per IP address (HTTP 429 beyond that), and the EU VIES registry throttles bursts per member state (MS\_MAX\_CONCURRENT\_REQ) and globally (GLOBAL\_MAX\_CONCURRENT\_REQ) — both are retried automatically with patient backoff and jitter. On Apify, outbound IPs are shared across many concurrent customer runs, so VIES's per-member-state quota can be consumed by unrelated traffic; this Actor additionally serializes its own VIES calls per country code regardless of this setting (two checks for the same country never run at once, but different countries do run in parallel), so raising this mostly helps when checking many DIFFERENT countries or many French SIREN/SIRET at once. Default of 2 is deliberately conservative after a real contention failure was observed on the Apify platform.

## Actor input object example

```json
{
  "identifiers": [
    "FR39356000000",
    "552049447"
  ],
  "includeFrenchEnrichment": true,
  "maxConcurrency": 2
}
```

# Actor output Schema

## `companyRecords` (type: `string`):

URL of the run's default dataset items (JSON array of company records, one per input identifier).

# 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 = {
    "identifiers": [
        "FR39356000000",
        "552049447"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("garonne/eu-vat-siren-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 = { "identifiers": [
        "FR39356000000",
        "552049447",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("garonne/eu-vat-siren-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 '{
  "identifiers": [
    "FR39356000000",
    "552049447"
  ]
}' |
apify call garonne/eu-vat-siren-check --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,garonne/eu-vat-siren-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/WmIvNzFTfKPKxEIjA/builds/JSlg6qzFh1O2c1mjZ/openapi.json
