# French Companies Enricher — SIREN/SIRET lookup (`mjeason/french-company-enrichment`) Actor

Enrich French companies from the official open-data registry (recherche-entreprises.api.gouv.fr): SIREN/SIRET lookup, KYC status, NAF activity, address, leaders and financials. Pay per enriched company.

- **URL**: https://apify.com/mjeason/french-company-enrichment.md
- **Developed by:** [Mike-jeason LECA](https://apify.com/mjeason) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

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

## French Companies Enricher (SIREN lookup for KYC & B2B prospecting)

Turn a list of French company names, SIRENs or SIRETs into a clean, structured dataset of
**legal registry records** — identity, head-office address, activity, headcount bracket,
officers and public compliance flags.

Data source: **[recherche-entreprises.api.gouv.fr](https://recherche-entreprises.api.gouv.fr)**,
the official free open-data search API over the French national business register
(SIRENE / INSEE, RNE, INPI and partner datasets). **No API key, no proxy, no login.**

Typical buyers:

| Buyer | What they do with it |
|---|---|
| B2B sales / lead-gen teams | Append SIREN, legal name, NAF code, headcount bracket and officers to a prospect list; filter out closed companies. |
| KYC / KYB & compliance teams | Verify that a counterparty exists, is active (`etat_administratif`), who its registered officers are, and whether it is an association, ESS or mission-driven company. |
| Due diligence / M\&A analysts | Check legal form, creation and closing dates, sites, published revenue for large companies. |
| Data teams | Resolve messy company names to canonical SIREN identifiers before joining internal data. |

***

### Input

```json
{
  "queries": ["552100554", "542051180", "boulangerie paris"],
  "maxResultsPerQuery": 25,
  "includeDirigeants": true,
  "includeEtablissements": false
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `queries` | array of strings | — (required) | Company names, 9-digit SIRENs, or SIRETs. The registry needs **at least 3 characters** per term; a term shorter than that is skipped with a warning instead of failing the run. |
| `maxResultsPerQuery` | integer 1–500 | `25` | Cap of companies kept per query, and therefore your cost cap: one company = one billed event. Name searches can match hundreds of companies (e.g. `boulangerie paris` → 988 matches), so this is what keeps a run cheap. |
| `includeDirigeants` | boolean | `true` | Adds the `dirigeants` array (people and corporate officers with role, birth year/month, SIREN for legal entities). |
| `includeEtablissements` | boolean | `false` | Adds the `etablissements` array of matching secondary sites (SIRET, address, activity, dates, coordinates). |

Each company is emitted **once** per run: if two queries match the same SIREN, the duplicate is
dropped before billing.

***

### Output

One dataset item per enriched company. Full example (real record, trimmed):

```json
{
  "siren": "552100554",
  "siren_conserve": null,
  "nom_complet": "PEUGEOT SA",
  "nom_raison_sociale": "PEUGEOT SA",
  "sigle": null,
  "etat_administratif": "C",
  "date_creation": "1955-01-01",
  "date_fermeture": "2021-01-16",
  "nature_juridique_code": "5699",
  "categorie_entreprise": null,
  "tranche_effectif_salarie_code": "NN",
  "annee_tranche_effectif_salarie": null,
  "activite_principale_naf": "70.10Z",
  "activite_principale_naf25": null,
  "section_activite_principale": "M",
  "nombre_etablissements": 5,
  "nombre_etablissements_ouverts": 0,
  "tva": null,
  "siege": {
    "siret": "55210055400054",
    "adresse": "RTE DE GIZY 78140 VELIZY-VILLACOUBLAY",
    "complement_adresse": null,
    "code_postal": "78140",
    "commune_code_insee": "78640",
    "commune": "VELIZY-VILLACOUBLAY",
    "departement": "78",
    "region": "11",
    "epci": "247800584",
    "pays_etranger": null,
    "latitude": "48.774126458",
    "longitude": "2.2219642076",
    "activite_principale": "70.10Z",
    "etat_administratif": "F",
    "date_creation": "2020-04-27",
    "date_fermeture": "2021-01-16",
    "tranche_effectif_salarie_code": "NN",
    "nom_commercial": null,
    "enseignes": null
  },
  "complements": {
    "est_entrepreneur_individuel": false,
    "est_association": false,
    "est_service_public": null,
    "est_administration": false,
    "egapro_renseignee": true,
    "est_ess": false,
    "est_societe_mission": false,
    "est_organisme_formation": false,
    "est_qualiopi": false,
    "convention_collective_renseignee": false,
    "liste_idcc": null,
    "est_avocat": false,
    "est_siae": false,
    "type_siae": null,
    "est_patrimoine_vivant": false,
    "identifiant_association": null
  },
  "finances": { "2019": { "ca": 74731000000, "resultat_net": 3584000000 } },
  "query": "552100554",
  "source_url": "https://annuaire-entreprises.data.gouv.fr/entreprise/552100554",
  "source_api": "https://recherche-entreprises.api.gouv.fr/search?q=552100554",
  "retrieved_at": "2026-10-26T19:12:44+00:00",
  "dirigeants": [
    {
      "type_dirigeant": "personne physique",
      "nom": "ANTUNES TAVARES DIAS",
      "prenoms": "CARLOS",
      "annee_de_naissance": "1958",
      "date_de_naissance": "1958-08",
      "qualite": "Président du directoire",
      "nationalite": null,
      "siren": null,
      "denomination": null
    }
  ],
  "confidence": 0.95
}
```

#### Field reference

| Field | Type | Description |
|---|---|---|
| `siren` | string | 9-digit national company identifier. Stable join key. |
| `nom_complet` | string | Legal name as published (falls back to `nom_raison_sociale`, then the SIREN itself if the registry record is empty). |
| `nom_raison_sociale`, `sigle` | string | null | Registered name and acronym. |
| `etat_administratif` | `"A"` | `"C"` | null | Company status: **A** = active, **C** = ceased. Filter on this before prospecting. |
| `date_creation`, `date_fermeture` | ISO date | null | Creation and, when applicable, closing date. |
| `nature_juridique_code` | string | null | INSEE legal-form code (e.g. `5699` = société européenne). |
| `categorie_entreprise` | `PME`/`ETI`/`GE` | null | Size category, published only for part of the register. |
| `tranche_effectif_salarie_code` | string | null | Headcount bracket code (INSEE scale, e.g. `02`, `11`, `51`; `NN` = not available). |
| `annee_tranche_effectif_salarie` | string | null | Year the headcount bracket refers to. |
| `activite_principale_naf` | string | null | NAF activity code of the company. |
| `activite_principale_naf25`, `section_activite_principale` | string | null | NAF 2025 code when published, and the NAF section letter. |
| `nombre_etablissements`, `nombre_etablissements_ouverts` | integer | Total and currently open sites. |
| `tva` | string | null | Intra-EU VAT number when published. |
| `siege` | object | Head office: `siret`, `adresse`, `complement_adresse`, `code_postal`, `commune` (city name), `commune_code_insee`, `departement`, `region`, `epci`, `pays_etranger`, `latitude`, `longitude`, `activite_principale`, `etat_administratif`, `date_creation`, `date_fermeture`, `tranche_effectif_salarie_code`, `nom_commercial`, `enseignes`. |
| `complements` | object | Public flags: `est_entrepreneur_individuel`, `est_association`, `est_service_public`, `est_administration`, `egapro_renseignee`, `est_ess`, `est_societe_mission`, `est_organisme_formation`, `est_qualiopi`, `convention_collective_renseignee`, `liste_idcc`, `est_avocat`, `est_siae`, `type_siae`, `est_patrimoine_vivant`, `identifiant_association`. |
| `finances` | object | null | Published accounts by year: `{ "2019": { "ca": …, "resultat_net": … } }`. Only for companies that file public accounts. |
| `dirigeants` | array | Present when `includeDirigeants` is true. Each entry: `type_dirigeant` (`personne physique` / `personne morale`), `nom`, `prenoms`, `annee_de_naissance`, `date_de_naissance` (YYYY-MM), `qualite`, `nationalite`, `siren`, `denomination`. |
| `etablissements` | array | Present when `includeEtablissements` is true. Each entry: `siret`, `adresse`, `code_postal`, `commune`, `commune_code_insee`, `departement`, `region`, `epci`, `activite_principale`, `etat_administratif`, `est_siege`, `date_creation`, `date_fermeture`, `tranche_effectif_salarie`, `annee_tranche_effectif_salarie`, `latitude`, `longitude`, `nom_commercial`, `enseignes`. |
| `query` | string | The input query that produced this record. |
| `source_url` | string | Human-verifiable page on the official `annuaire-entreprises.data.gouv.fr`. |
| `source_api` | string | Exact API request used, for reproducibility. |
| `retrieved_at` | ISO 8601 | Fetch timestamp (UTC). |
| `confidence` | number 0–1 | **Data-completeness score**, not a probability that the company exists. It is the weighted share of key registry fields actually populated (name, SIRET, postal code, address, NAF, creation date, status, city, legal form, headcount bracket), ignoring the registry's `NN` "unknown" sentinel. A sparse record (unknown SIREN) scores low; a fully documented one scores 1.0. |

***

### Pricing

Suggested **pay-per-event** pricing: one event `company-enriched` per dataset item.

| Volume | Price per event | Price per 1,000 results |
|---|---|---|
| Suggested | **$0.002** | **$2.00** |
| Low-volume / high-touch | $0.005 | $5.00 |
| High-volume data teams | $0.001 | $1.00 |

The underlying API is free and open data, so the price covers the enrichment, normalisation,
pagination, retries, deduplication and the stable schema (codes normalised, num→string,
nulls kept explicit, `commune` split into code + name). Comparable Apify actors in the
company-data niche charge $1–$5 per 1,000 records. Runs with zero results are never billed.

***

### Known limits (verified against the live API)

- **No human-readable labels for codes.** The API returns `nature_juridique` (`5699`),
  `tranche_effectif_salarie` (`NN`, `02`…), `departement` (`78`) and `region` (`11`) as
  **codes only** — no `libelle_*` for these, so this Actor does not invent one. Bring your own
  lookup table if you need "SARL", "50–99 employees" or "Île-de-France". City names *are*
  available (`siege.commune`), because the API's `commune` field is the INSEE code and its
  `libelle_commune` is the name — this Actor exposes both.
- **`per_page` is capped at 25** by the API (`per_page=100` → HTTP 400), so results above 25
  require extra paged requests. Deep pagination is capped at 40 pages per query.
- **Queries must be at least 3 characters.** Shorter terms return HTTP 400; the Actor logs a
  warning, records `error: "invalid_query"` in the run summary and continues.
- **Empty records exist.** An unknown SIREN can return a shell record with a SIREN and dates
  but no name or address. Those items keep `nom_complet` = the SIREN, carry no fabricated
  values, and score a low `confidence` (< 0.5) so you can filter them out.
- **Officers are partial.** Only published `dirigeants` appear, and natural persons may opt
  out of public diffusion — `nom` can be `null`. Roles are in French, as published
  (`Président du directoire`, `Gérant`, `Commissaire aux comptes`…).
- **`etablissements` only lists matching sites**, not the full site list of the company. For
  the full list, use the company's `siren` with the same API's `q=` search.
- **`finances` is sparse** — only companies filing public accounts have it, and only for the
  years the registry exposes.
- **No rate-limit headers are published.** This Actor self-limits to 10 requests/second and
  retries twice (honouring `Retry-After` when present). HTTP 429 was observed during testing
  under burst load and was recovered transparently.
- **Public-sector / foreign entities** are only present insofar as the register carries them;
  `est_service_public` is frequently `null` rather than `false`.

***

### Run locally

```bash
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
INPUT_JSON='{"queries":["552100554","danone"],"maxResultsPerQuery":5}' \
  .venv/bin/python -m src.main
## results land in ./storage/datasets/default/
.venv/bin/python -m pytest tests -q
```

`INPUT_JSON` is a local-development convenience only; on the Apify platform the Actor reads
its input from the standard `INPUT` key-value record.

### Layout

```
.actor/actor.json          Actor definition (input/output/dataset schema refs)
.actor/input_schema.json    Input JSON Schema (English, buyer-facing)
.actor/output_schema.json   Dataset + run-summary links
.actor/dataset_schema.json  Table views (overview, KYC)
actor -> .actor            Convenience symlink to the same files
src/main.py                 The Actor (stdlib HTTP + apify SDK, no third-party client)
Dockerfile                  apify/actor-python:3.12
tests/test_fields.py        Field-contract + exit-code tests against the live API
proof/                      Real run output and measurements
```

### Publishing (steps left for the account owner)

1. `npm i -g apify-cli && apify login` (a personal API token is required — not available on
   the machine this Actor was built on).
2. `apify push` from this directory.
3. In the Console → **Monetization**, enable pay-per-event with event `company-enriched` at
   $0.002, matching the runs' charged event name.
4. Publish to the Store with the suggested categories *Lead generation*, *Business data*,
   *KYC / compliance*.

# Actor input Schema

## `queries` (type: `array`):

One entry per company or list you want to enrich. Each entry can be a company name (e.g. "Danone" or "boulangerie Lyon"), an exact 9-digit SIREN (e.g. "552100554"), or a SIRET (the first 9 digits are used by the registry). Every company is emitted once, even if it matches several queries — you are not billed twice for it.

## `maxResultsPerQuery` (type: `integer`):

Upper bound of companies kept for each query. A SIREN query returns a single company; name searches can match hundreds of companies, so this number is also your cost cap: each returned company costs one billing event. The registry API pages 25 records at a time, so values above 25 trigger extra paged requests.

## `includeDirigeants` (type: `boolean`):

Add the `dirigeants` array with the people and legal entities registered as company officers (role/qualité, first and last name, year and month of birth when published, nationality, SIREN for corporate officers). Useful for KYC, beneficial-ownership screening and prospecting by decision maker. Note that only published officers appear; natural persons can opt out of public diffusion, in which case names are absent.

## `includeEtablissements` (type: `boolean`):

Add the `etablissements` array with the secondary sites that matched the search: SIRET, address, city, activity, employee bracket, opening and closing dates, and coordinates. Leave it off for pure company-level enrichment; turn it on to map branches, multi-site groups or local prospects. Only establishments that matched the query are returned by the registry, not the full site list.

## Actor input object example

```json
{
  "queries": [
    "552100554",
    "542051180",
    "danone"
  ],
  "maxResultsPerQuery": 25,
  "includeDirigeants": true,
  "includeEtablissements": false
}
```

# Actor output Schema

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

One item per enriched French company. See the dataset schema for every field.

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

Per-query counters (items emitted, API total_results, requests made, duplicates skipped) and elapsed time.

# 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 = {
    "queries": [
        "552100554",
        "542051180",
        "danone"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mjeason/french-company-enrichment").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 = { "queries": [
        "552100554",
        "542051180",
        "danone",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("mjeason/french-company-enrichment").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 '{
  "queries": [
    "552100554",
    "542051180",
    "danone"
  ]
}' |
apify call mjeason/french-company-enrichment --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mjeason/french-company-enrichment"
        }
    }
}
```

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/AxEQyCwDFRFP7exED/builds/dg3te2U7h9kI370Pr/openapi.json
