# French Company Search – SIREN, SIRET & NAF (Sirene) (`far003/french-company-search-sirene`) Actor

Search French companies by name, SIREN, SIRET, NAF code, department or legal form in the open Sirene data. Legal entities only: no directors, no sole traders. Licence attribution in every record, optional new-or-changed mode.

- **URL**: https://apify.com/far003/french-company-search-sirene.md
- **Developed by:** [Francesco Antonio Russo](https://apify.com/far003) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 90.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.50 / 1,000 companies

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 Company Search – SIREN, SIRET & NAF (Sirene)

Look up French companies by name, **SIREN**, **SIRET**, **NAF** (APE) activity code, department, postal code, legal form and size, and get clean, flat JSON records. The data is the open **Sirene** register published by INSEE, read through the public *Recherche d'entreprises* API (no API key, no scraping).

Three things make this Actor different on purpose:

1. **Legal entities only, and no people as such.** No directors, shareholders or elected officials are requested, delivered or logged; no sole traders (*entrepreneurs individuels*) and no groups of natural persons (indivisions and de-facto companies *entre personnes physiques*, see the exact list below); nothing marked as not publicly diffusible. What is delivered is the Sirene record of a legal entity, and a legal entity's registered name and the head-office address can contain a person's name (a SAS named after its founder, a civil real-estate company named after its owner): they are public company data, delivered as the source publishes them (see "Personal data (GDPR)" below).
2. **The licence attribution is in every record.** The Sirene data is released under the Licence Ouverte 2.0, which requires the source and the update date to be named. Each record carries `source`, `sourceLicense`, `sourceUpdatedAt` and `retrievedAt`, so the attribution travels with the data into your CRM or warehouse.
3. **A "new or changed only" mode.** Schedule the Actor and receive only the companies that appeared or changed since your last run, with the list of fields that changed.

> This Actor is an independent tool. It is **not** an official service and is not endorsed by, or affiliated with, INSEE, DINUM, Etalab or any French public body. The data is informational; it is not a legal extract (no Kbis) and must not be used as proof of a company's status.

### Who it is for

- **KYB and onboarding checks**: confirm that a SIREN/SIRET exists, is active, and what its legal form, activity code and head office are.
- **CRM enrichment and deduplication**: turn a list of company names or identifiers into normalised records with stable keys.
- **Market mapping and lead lists**: "all active SAS with an establishment in Lyon and 10 to 49 employees in NAF 62.01Z", delivered with the head-office address and coordinates and the list of their establishments in Lyon.
- **Monitoring**: run it on a schedule with `onlyNewOrChanged` to follow a segment over time.

### Input

| Field | What it does |
|---|---|
| `queries` | Company names (free text), SIREN (9 digits), SIRET (14 digits, spaces allowed) or a French VAT number (`FR` + 2-digit key + SIREN, for example `FR27552032534`, read as its SIREN; only the arithmetic of the key is checked, not whether the number is registered for VAT, and no VAT data is delivered). SIREN and SIRET are checked with the Luhn algorithm; an invalid number produces a free `invalid_identifier` record and no request. Leave empty to search with filters only. A **name search delivers only companies that carry the words of your query in their own names** (legal name, acronym, trade names of the head office or of an establishment, accents and case ignored, ligatures spelled out so *Sœur* = *Soeur* and *Straße* = *Strasse*, whole words only: *Tech* does not find TECHER, *Total Energies* does not find ENERGIE TOTALE; a plural *s* or *x* tolerated in either direction on words of more than 3 letters, and words written together or apart are the same, *Total Energies* = TOTALENERGIES, see "Other spellings" below; the words of your query may come from different names of the same company, for example the legal name and the trade name of a closed establishment): the source also matches the text against company directors, and a company found only through a director is not delivered. Those results are free and counted in the SUMMARY as `notMatchingName`. **Legal-form words are optional**: the Sirene name is "DANONE", not "DANONE SA", so `sa`, `sas`, `sasu`, `sarl`, `eurl`, `snc`, `sca`, `scs`, `sci`, `sel`, `selarl`, `selas`, `scp`, `scop`, `se` (also written `S.A.`, `S.A.S.`...) and the generic words `societe`, `ste`, `ets`, `etablissement(s)` are not needed to match; a company that has them in its name comes first. The source matches the text against its own fields, not only the Sirene name (live check: "Société Danone" does not find DANONE), so a name that contains such words is also asked without them, after the query as you typed it, until `maxResultsPerQuery` is reached (a company met twice counts once; the `query` of the rows stays what you typed). **Other spellings.** The source searches word by word: *Total Energies* does not find TOTALENERGIES and *SaintGobain* does not find SAINT GOBAIN. So a name of several words is also asked **written together** (*Total Energies* is also asked as *TotalEnergies*), and a name with a capital letter inside a word is also asked **written apart** (*SaintGobain* is also asked as *Saint Gobain*). The query as you typed it always comes first; the other spellings are asked only if `maxResultsPerQuery` is not reached yet, each other spelling is one more search (up to 3 more per query, each can read several pages), but all of them share the page ceilings of a name search below, the speed limit and the spending limit. A word typed in one block without capitals (*saintgobain*) is never cut, because there is no safe place to cut it: if the source finds nothing, try the words apart. "Danone SA", "Danone SAS" and "Société Danone" therefore deliver DANONE (552032534). Every other word is required, `groupe` included: "Danone France" does **not** deliver DANONE (its names do not contain *France*), it delivers the companies whose names do, such as DANONE PRODUITS FRAIS FRANCE. A query made only of optional words (for example `SCI`) requires them. SIREN and SIRET are not subject to this rule. A name of one character, or made only of articles (`a`, `de la`), is refused with a free `invalid_identifier` row. Identifiers copied from documents or spreadsheets are read when the recovery is certain: `552 032 534 RCS Paris` (the RCS number is the SIREN) and `5.52032534E8` (scientific notation) give the SIREN if the number passes the check; a number of 8 or 13 digits is refused with a hint (*leading zero lost?*, and the valid number if adding the zero gives one). |
| `departments`, `postalCodes`, `communeCodes`, `regions` | Where the company has **an establishment**, not only its head office (department codes like `75`, `2A`, `971`; 5-digit postal codes, a space inside as in `69 001` is accepted; INSEE commune codes; INSEE region codes). See "Geographic filters" below. **Paris `75056`, Lyon `69123` and Marseille `13055`** are expanded automatically to their arrondissement codes (`75101`-`75120`, `69381`-`69389`, `13201`-`13216`), because Sirene registers the establishments of these cities by arrondissement and almost none under the city code; the SUMMARY says so in the `warnings` of the search, and a company present in several arrondissements is delivered, and charged, once. |
| `nafCodes`, `nafSections` | Main activity: **NAF rev. 2** codes like `62.01Z` (the 732 codes of the current nomenclature; any other value, **including a NAF 2025 code such as `70.10Y`**, stops the run before any request with a message that says so), or section letters `A` to `U`. |
| `legalFormCodes` | INSEE legal form codes like `5710` (SAS), `5499` (SARL), `5599` (SA). Codes starting with `1` (sole traders) and the four codes of groups of natural persons (`2110`, `2210`, `2310`, `2385`) are refused. |
| `companyCategories` | `PME`, `ETI`, `GE`. |
| `employeeRanges` | INSEE employee range codes (`11` = 10-19, `12` = 20-49, ...). |
| `status` | `any` (default), `active` or `closed`. |
| `maxResultsPerQuery` | Default 20 (a cheap first try; raise it when the sample looks right). Companies taken from each query (or from the filter search). Privacy exclusions, results left out because the name does not match, duplicates and (with `onlyNewOrChanged`) unchanged companies do not count. |
| `onlyNewOrChanged` | Default off. See below. |
| `requestsPerSecond` | Default 2, maximum 2 (the Actor stays gentle with a public service). |

Wrong values (a malformed department, NAF code, postal code...) stop the run immediately with a clear message, before any request is made.

Filters apply to name searches and to filter-only searches. A SIREN or SIRET is looked up as it is: filters are not applied to it.

Example input:

```json
{
  "queries": ["DANONE", "552032534"],
  "departments": ["75"],
  "nafCodes": ["62.01Z"],
  "status": "active",
  "maxResultsPerQuery": 100
}
```

### Output

One dataset record per company. Every field is always present (`null` when the source has no value) and has a stable type. This is a real record, obtained by looking up SIREN `552032534`:

```json
{
  "status": "active",
  "query": "552 032 534",
  "queriedSiret": null,
  "establishmentStatus": null,
  "establishmentClosedDate": null,
  "requestedEstablishment": null,
  "siren": "552032534",
  "siret": "55203253400703",
  "name": "DANONE",
  "legalName": "DANONE",
  "acronym": null,
  "legalFormCode": "5599",
  "legalFormLabel": "SA à conseil d'administration (s.a.i.)",
  "nafCode": "70.10Z",
  "nafCodeNaf25": "70.10Y",
  "nafSection": "M",
  "companyCategory": "GE",
  "employeeRangeCode": "42",
  "employeeRangeLabel": "1 000 à 1 999 salariés",
  "employeeRangeYear": 2024,
  "headOfficeIsEmployer": true,
  "creationDate": "1955-01-01",
  "closureDate": null,
  "establishmentCount": 19,
  "openEstablishmentCount": 3,
  "address": "59-61 RUE LA FAYETTE 75009 PARIS",
  "postalCode": "75009",
  "city": "PARIS",
  "cityCode": "75109",
  "department": "75",
  "region": "11",
  "latitude": 48.8763540066087,
  "longitude": 2.34353640229216,
  "headOfficeCreationDate": "2025-08-19",
  "headOfficeStatus": "active",
  "headOfficeInArea": null,
  "matchedEstablishments": null,
  "sourceUpdatedAt": "2026-09-10T13:14:57",
  "change": null,
  "changedFields": null,
  "duplicateOf": null,
  "note": null,
  "source": "INSEE – base Sirene (www.insee.fr), retrieved through the API Recherche d'entreprises (DINUM)",
  "sourceLicense": "Licence Ouverte 2.0 / Open Licence 2.0 (Etalab)",
  "retrievedAt": "2026-10-13T13:12:00.000Z"
}
```

Notes on the fields:

- `siren` identifies the legal unit; `siret` is the SIRET of its **head office**. If you look up a SIRET, the company is delivered with `queriedSiret` set to the number you asked for (the head office or another establishment); each SIRET of the input gets its own record, even when its SIREN or another SIRET of the same company is in the input too.
- **`requestedEstablishment`: the establishment you asked for.** In a SIRET lookup, the top-level fields (`siret`, `address`, `postalCode`, `city`, `nafCode`, employee range, coordinates...) stay those of the **company and its head office**; the establishment of the SIRET you typed is in the `requestedEstablishment` object: `siret`, `isHeadOffice`, `status`, `closedDate`, `address`, `postalCode`, `city`, `cityCode`, `department`, `nafCode`, `employeeRangeCode`, `employeeRangeLabel` and `tradeNames`, all read from the source (the source gives no department for an establishment: it is read from the first digits of its commune code). It is `null` for every other kind of query. If the source marks the establishment as not publicly diffusible, `requestedEstablishment` is `null`, a `note` says so and the company record is still delivered (and charged, it is real data).
- **SIRET lookups and closed establishments.** For a SIRET lookup, `establishmentStatus` (`active` / `closed`) and `establishmentClosedDate` describe the establishment you asked for, while `status` is always the status of the legal unit. A closed establishment of an active company is delivered (and charged, it is real data) with `establishmentStatus: "closed"`, its closing date and a `note` saying so. In other searches both fields are `null`.
- `name` is the source's full name (*nom complet*): it can repeat the acronym or a trade name in brackets, for example `PYRAMIDE CONSTRUCTION GROUP (PCG)`; `legalName` is the bare legal name and `acronym` the acronym.
- `legalFormLabel` and `employeeRangeLabel` come from the INSEE tables shipped with the Actor (`data/`, source written in each file), in French.
- `nafCode` is NAF rev. 2 (the code in force today) and **is the one to use in the `nafCodes` filter**; `nafCodeNaf25` is the NAF 2025 code when Sirene already publishes it, and the API does **not** accept it as a filter (copying it into `nafCodes` is refused with a clear message). The list of accepted rev. 2 codes is `data/naf-rev2.json` (read from the API on 2026-10-14); it will be updated when the API accepts the NAF 2025 codes.
- **Geographic filters.** A department, postal-code, commune or region filter matches every company that has *an establishment* in that area. The address, `postalCode`, `city`, `department`, coordinates and `siret` of the record are always those of the **head office**, which can be elsewhere (a Paris-based group with a shop in Lyon matches "Lyon" with a Paris head office). So with such a filter every record also carries `headOfficeInArea` (`true` if the head office itself is in the area) and `matchedEstablishments`: up to 5 establishments the source reports inside the area, each with `siret`, `address`, `postalCode`, `city`, `status` and `isHeadOffice` (head office first, then open, then closed; the source itself reports at most 10). Without a geographic filter both fields are `null`. When a very large search is split automatically, the list shows the establishments found in the first part of the split that met the company.
- `latitude` and `longitude` are numbers (the source sends them as text).
- `status` is the administrative status of the legal unit (`active` / `closed`); `headOfficeStatus` that of the head-office establishment; `establishmentStatus` that of the establishment of a SIRET lookup.
- `headOfficeIsEmployer` is the Sirene employer flag (*caractère employeur*) of the **head-office establishment** (`true` / `false`, `null` when Sirene does not say). The flag is empty at legal-unit level in the source, so the head office's is the one delivered.
- `duplicateOf` is filled only on `duplicate` rows (see below): the earlier entry that already handled the company.
- `sourceUpdatedAt` is INSEE's own update date for the unit, exactly as published (no time zone).
- Only fields of INSEE origin are delivered. Flags from other registers that the API also exposes (RGE, ESS, organic, elected officials...) are deliberately left out.

#### Records without company data (always free)

**Every entry of `queries` produces at least one row**, so a list of 200 identifiers comes back as 200 or more rows you can join on `query`. Only the identifier you typed (and a `note`) is filled in; every other field is `null`, and none of these rows is ever charged.

| `status` | Meaning |
|---|---|
| `not_found` | No such SIREN/SIRET in the data served by the API. For a name or filter search: the search had no result to deliver (the `note` says how many results the source reported and why none was delivered). |
| `invalid_identifier` | The number fails the SIREN/SIRET check (or the key of a VAT number), has a length that is neither 9 nor 14 (the note asks whether a leading zero was lost), or the entry is made only of symbols such as `&`, or is a single character or only articles (`a`, `de la`). No request was made. |
| `excluded_individual` | The SIREN/SIRET belongs to a sole trader or to a group of natural persons (legal forms `1xxx`, `2110`, `2210`, `2310`, `2385`). Not delivered, by design. |
| `excluded_privacy` | The unit is not fully publicly diffusible. Not delivered. |
| `duplicate` | The entry repeats an earlier entry of the same input (the same SIRET written twice, a SIREN and its VAT number...), or its company (SIREN) was already handled by an earlier entry of this run: a SIREN found by an earlier name search, a SIREN given twice... `siren` and `duplicateOf` (the earlier entry) tell which; the company is delivered once. **A SIRET is never a duplicate of a SIREN, nor of another SIRET of the same company**: it is another entity and gets its own record, with `requestedEstablishment`, charged like any record with data (a SIREN and one of its SIRET, or two SIRET of one company, give two records). A name or filter search whose companies were all handled by earlier entries also gets one such row. |
| `unchanged` | `onlyNewOrChanged` mode: the SIREN/SIRET is already delivered by an earlier run and nothing changed since. |

A name or filter search that delivers at least one company has no such row, and in `onlyNewOrChanged` mode a name or filter search whose companies are all unchanged delivers nothing at all (the SUMMARY counts them): a scheduled run only carries news. An entry that could not be answered (the source kept failing) or was not run because the spending limit was reached has no row: it is `failed` or `skipped` in the SUMMARY.

#### Run summary

A `SUMMARY` record in the key-value store lists, for each query: how many results the API reported (`totalFound`, added up in `foundByApi` of the totals: the source also matches a name against company directors, so both numbers count those results too, although they are never delivered; neither is the number of companies you can expect), how many were delivered, how many were skipped as unchanged or as duplicates, how many were excluded for privacy (a count only, never a name), how many were left out because the query words are in none of their names (`notMatchingName`), how many requests failed, plus the `stopReason` and `finishedAt`. It is also written while the run is going on, so a run that is stopped still leaves an honest summary.

### What this Actor does not contain, and why

- **No directors, shareholders or beneficial owners.** They are natural persons.
- **No sole traders / individual entrepreneurs.** Their name and address are personal data. A search never returns them: the API is asked to filter them out, and anything that still arrives with a legal form starting with `1`, an individual-entrepreneur flag, or a diffusion status other than "fully diffusible" is discarded by the Actor itself. Names of discarded results are never logged.
- **No groups of natural persons.** These legal forms have no legal personality of their own and are named after the people who form them, so they are discarded as well (free, counted in `excludedPrivacy`; a SIREN/SIRET lookup gives an `excluded_individual` record). The exact list of excluded legal forms:
  - every code starting with `1` (sole traders and other individuals);
  - `2110` Indivision entre personnes physiques;
  - `2210` Société créée de fait entre personnes physiques;
  - `2310` Société en participation entre personnes physiques;
  - `2385` Société en participation de professions libérales (its members are individual professionals).
    The variants *avec personne morale* (`2120`, `2220`, `2320`) include a legal entity and are delivered.
- **No financial statements, no VAT data, no register extracts.**

This is a design choice, not a limitation to be configured away.

### Source and licence

The data is the Sirene register of INSEE, served by the API *Recherche d'entreprises* (DINUM), which is open and needs no key. Sirene is published under the **Licence Ouverte 2.0** (Etalab), which allows commercial reuse provided that the source and the date of the last update are mentioned: that is what `source`, `sourceLicense` and `sourceUpdatedAt` are for. Keep them when you pass the data on, and do not suggest that INSEE, DINUM or the French State endorse your product.

### Only new or changed companies

With `onlyNewOrChanged` on, the Actor remembers, between runs, a fingerprint of the fields it delivered for every SIREN, and one for every SIRET looked up (the fingerprint of a SIRET also covers the state of its establishment, `establishmentStatus` and `establishmentClosedDate`: a SIRET that closes is delivered again as `changed`) (in a named key-value store, `fr-company-search-state`, shared by all your runs of this Actor). Then:

- a company never delivered before is delivered with `change: "new"`;
- a company whose delivered data differs from the last delivery is delivered with `change: "changed"` and `changedFields` listing the fields;
- an unchanged company is skipped and **not charged**, and it does **not use up `maxResultsPerQuery`**: the Actor keeps reading the results until it has delivered as many new or changed companies as you asked for, or until the search is exhausted. So a segment larger than your limit is worked through in successive runs (5 new, then the next 5, and so on).
- **Page ceiling.** To keep the load on the source reasonable, in this mode the Actor reads at most **500 pages (12,500 results) per query**. If that ceiling stops a search before it has found enough new or changed companies, the query is marked `partial` in the SUMMARY (`pageCapReached: true`, `totals.stoppedByPageCeiling`) with a warning, and the log says so: narrow the filters (department, postal code, NAF code) to reach the rest.

**Reset the memory.** To start again from zero (everything is "new" again, and charged again), delete the named key-value store `fr-company-search-state`: on the platform, Storage > Key-value stores > that store > Delete; in a local run, delete the folder `key_value_stores/fr-company-search-state`.

**Cost of a first run.** In this mode too, the first run delivers every company it meets as "new" and is charged like a normal run: a first run that delivers 20 companies costs 20 times the price of one Company event (for an event priced at 0.0025, 20 x 0.0025 = 0.05, in the currency of the price shown on the Actor page). With the default of 20 per query, a segment of 500 companies is worked through in 25 successive runs, and the next runs only charge the news.

Only what was actually delivered is remembered, so a run stopped by its budget or by an error does not hide anything: the rest is "new" next time. The filters are not part of the memory: a company met again under different filters is compared as it is. INSEE's own update date (`sourceUpdatedAt`) does not count as a change by itself.

### How much does it cost?

**$2.50 per 1,000 companies** ($0.0025 per company record with data). Everything else is free: not found, invalid identifiers, duplicates, unchanged companies, excluded units, failed requests. There is no start fee and no fee per query.

| You run | Records with data | Cost at most |
|---|---|---|
| 200 SIREN/SIRET from your CRM | ≤ 200 | **$0.50** |
| 100 company names, default `maxResultsPerQuery` 20 | ≤ 2,000 | **$5.00** (`maxResultsPerQuery: 5`: ≤ $1.25) |
| A segment: active SAS in one department and NAF section, 1,000 results | 1,000 | **$2.50** |
| The same segment again a week later with `onlyNewOrChanged` | only what changed | usually a few cents |

To keep spending under control: `maxResultsPerQuery` caps each name or filter search; the **maximum cost per run** you set in Apify Console (or `maxTotalChargeUsd` in the API) is never exceeded — the Actor stops before it. For lead lists use `status: "active"`: closed companies have data and are charged.

### What is charged

One event, **Company**: a record that carries company data (`status` `active` or `closed`) and is delivered to the dataset. Not charged: every row whose `status` is not `active` or `closed` (`not_found`, `invalid_identifier`, `excluded_individual`, `excluded_privacy`, `duplicate`, `unchanged`), companies skipped as unchanged inside a search, results left out for privacy or because the name does not match, and failed requests. The Actor never delivers more records than your spending limit allows and stops calling the API once the limit is reached. If the Actor is started with pay-per-event pricing that does not include the Company event, it refuses to run.

### Limits to know

- **10,000 results per search.** The API never returns more than 10,000 companies for one search. If a search reports 10,000 and you asked for more, the Actor splits it automatically (by commune when you listed several, Paris, Lyon and Marseille arrondissements included; otherwise by department, or by the departments you listed, then by NAF section or NAF code) and removes duplicates by SIREN. If a slice still reaches the limit, the first 10,000 of that slice are delivered and the summary says so; narrow the filters (postal code, NAF code, legal form) to reach the rest. The overseas collectivities outside the five overseas departments are not part of the automatic split.
- **Speed.** The public API allows at most 7 requests per second per IP address, and 30 per second for all users of the same network; it can lower these limits under load. The Actor sends one request at a time, never more than `requestsPerSecond` (default and maximum 2), and identifies itself in the `User-Agent`. On a `429` it waits for the `Retry-After` delay (at most 60 s) and retries up to 4 times, slowing down for the rest of the run; on a server error it retries twice. A part of a search that still fails is marked `failed` in the summary, free of charge, and the run continues; five failed requests in a row stop the run. No proxy and no IP rotation are used.
- **Page ceilings of a name search** (with or without `onlyNewOrChanged`). The source also answers a name with companies found through their directors, which are never delivered, so a name with few real matches (a person's name, say) could make the Actor read hundreds of pages for a handful of companies. A name search therefore stops after **5 pages in a row without a single company that has your words in its names**, and anyway after **20 such pages in total** (a page that brings at least one match, and an empty page, does not count; 500 results read without any match at most). When a ceiling stops a search, the query is `partial` in the SUMMARY (`pageCapReached: true`, `totals.stoppedByPageCeiling`) with a warning in the SUMMARY and in the log, the companies found before are delivered, and a search that found nothing gets a `not_found` row whose `note` says the search was stopped. Add filters (department, postal code, NAF code) or a more distinctive name to go further. Searches by filters only and SIREN/SIRET lookups have no such ceiling.
- **25 results per request**, one page at a time.
- **No filter on creation date**: the API does not offer one. Use `onlyNewOrChanged` to follow new companies in a segment.
- **No guarantee from the source.** The API has no published service-level commitment. Records can lag the official register, and recently created or modified units may not be there yet.
- **Not every French entity is public.** Units that are not diffusible do not appear in the API at all.

### FAQ

**Why is a company I know "not found"?** The search reads the Sirene names (legal name, acronym, trade names and signs). Articles, prepositions and legal forms in your text are optional ("Le Garage Dupont" finds GARAGE DUPONT, "Danone SA" finds DANONE), but every other word must be in a name of the company. Try the SIREN, or fewer words.

**Is the first result of a name search the company I mean?** Not always. Results come in the order of the source, which favours names that match the words exactly: "LVMH" first returns two small property companies called LVMH, and "Michelin" puts the tyre maker sixth, after namesakes. To identify one company for sure, search by SIREN or SIRET; with names, keep several results (`maxResultsPerQuery` 5-10) and pick by `legalFormLabel`, `companyCategory`, `openEstablishmentCount`, address or `status`.

**With a place filter, is `status: active` about the place?** `status` is the company's. With `status: "active"` and a department, postal code or commune filter, a company whose establishments in that area are all closed is left out (free, counted in the SUMMARY as `noOpenEstablishmentInArea`). Each record says whether the head office is in the area (`headOfficeInArea`) and lists up to 5 matching establishments with their status.

**Why are sole traders missing?** By design: sole traders (legal forms 1xxx), groups of natural persons and units without legal personality are never delivered, because their names are people's names. Only legal entities are returned.

**How fresh is the data?** It comes from the public Recherche d'entreprises API, which follows the Sirene database (INSEE) with a delay of about one day.

**How do I call it from my code?** `POST https://api.apify.com/v2/acts/far003~french-company-search-sirene/run-sync-get-dataset-items?token=<YOUR_TOKEN>` with a JSON body such as `{"queries": ["DANONE", "552032534"]}` returns the records directly. With `apify-client` for JavaScript: `const run = await client.actor('far003/french-company-search-sirene').call({ queries: ['DANONE'] }); const { items } = await client.dataset(run.defaultDatasetId).listItems();`

**Something looks wrong?** Open an issue on the Actor's page (Issues tab) with the query and the run ID: every report is read.

### Personal data (GDPR)

The Actor delivers no directors, no sole traders and no groups of natural persons (see above), but it cannot make the registered name or the head-office address of a legal entity free of personal data: some company names contain a person's name (for example a SAS or a civil real-estate company named after its owner), and the head-office address can be a private one. Nothing in the output tells you when that is the case. You remain responsible for how you use the data, for your own lawful basis, and for respecting the conditions of the Licence Ouverte 2.0 and of the French data-protection rules, including for prospecting. Nothing here is legal advice.

### Running locally

```
npm install
npm test            # unit and end-to-end tests against a local fake of the API
echo '{"queries":["552032534"]}' > storage/key_value_stores/default/INPUT.json
npm start           # the records land in storage/datasets/default
```

To keep the storage of a local run in another folder, set `CRAWLEE_STORAGE_DIR` (the Apify SDK 3 ignores the older `APIFY_LOCAL_STORAGE_DIR`): put `INPUT.json` in `$CRAWLEE_STORAGE_DIR/key_value_stores/default/` and start with `CRAWLEE_STORAGE_DIR=/some/folder npm start`. A local run sends real requests to the public API (at most `requestsPerSecond` per second).

# Actor input Schema

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

Free text (a company name: every word you type must be a whole word of one of its names), a SIREN (9 digits), a SIRET (14 digits, spaces allowed) or a French VAT number (FR + 2-digit key + SIREN, read as its SIREN). SIREN and SIRET are checked with the Luhn algorithm: an invalid number gives a free "invalid_identifier" record, no request. A SIREN/SIRET of a sole trader or of a group of natural persons gives a free "excluded_individual" record without any data. A name search delivers only companies that have the words of the query in one of their own names (legal name, acronym, trade names); legal-form words (SA, SAS, SARL, SCI...) and "societe" are optional, so "Danone SA" finds DANONE; a name of several words is also searched written together ("Total Energies" also as "TotalEnergies"), and a name with a capital inside a word also apart ("SaintGobain" also as "Saint Gobain"); results found only through a company director are skipped for free. Leave empty to search with the filters below only.

## `departments` (type: `array`):

For example 75, 13, 2A, 971. The search matches companies with an establishment in these departments.

## `postalCodes` (type: `array`):

5 digits, for example 75009 (a space inside, as in 69 001, is accepted).

## `communeCodes` (type: `array`):

5 characters, for example 75109 (Paris 9e). The city codes of Paris (75056), Lyon (69123) and Marseille (13055) are replaced by the codes of their arrondissements.

## `regions` (type: `array`):

INSEE region codes, for example 11 (Île-de-France), 93, 84.

## `nafCodes` (type: `array`):

NAF rev. 2 codes, for example 62.01Z: use the nafCode value of the records. NAF 2025 codes (the nafCodeNaf25 value, for example 70.10Y) are not accepted by the API yet and are refused before any request.

## `nafSections` (type: `array`):

Section letters A to U, for example J (information and communication).

## `legalFormCodes` (type: `array`):

4-digit INSEE codes, for example 5710 (SAS), 5499 (SARL), 5599 (SA). Codes starting with 1 (sole traders) 2110, 2210, 2310, 2385 (groups of natural persons) and 2400, 2800, 2900, 9210 (no legal personality) are refused.

## `companyCategories` (type: `array`):

PME (small and medium), ETI (mid-cap), GE (large).

## `employeeRanges` (type: `array`):

INSEE codes: 01 (1-2), 02 (3-5), 03 (6-9), 11 (10-19), 12 (20-49), 21 (50-99), 22 (100-199), 31 (200-249), 32 (250-499), 41 (500-999), 42 (1,000-1,999), 51 (2,000-4,999), 52 (5,000-9,999), 53 (10,000+), NN (not an employer).

## `status` (type: `string`):

Keep only active companies, only closed ones, or both. Direct SIREN/SIRET lookups are not filtered.

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

Companies taken from each query or filter search (privacy exclusions, name mismatches, duplicates and, in "only new or changed" mode, unchanged companies do not count). The API lists at most 10,000 companies per search; above that, the search is split automatically by department and then by NAF section.

## `onlyNewOrChanged` (type: `boolean`):

Remembers, between runs, what was delivered for each SIREN, and delivers only companies never delivered before ("new") or whose data changed ("changed", with the list of changed fields). Unchanged companies are skipped, are not charged and do not use up the maximum above; at most 500 pages (12,500 results) are read per query in this mode, and the SUMMARY says when that ceiling stopped a search. The memory is shared by all your runs of this Actor, whatever the filters.

## `requestsPerSecond` (type: `number`):

Speed towards the public API (official limit: 7 per second per IP, shared with other users of the same cloud network). Kept gentle on purpose: 2 is both the default and the maximum.

## Actor input object example

```json
{
  "queries": [
    "DANONE",
    "552032534"
  ],
  "status": "any",
  "maxResultsPerQuery": 20,
  "onlyNewOrChanged": false,
  "requestsPerSecond": 2
}
```

# Actor output Schema

## `companies` (type: `string`):

No description

## `location` (type: `string`):

No description

## `attribution` (type: `string`):

No description

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

No description

# 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": [
        "DANONE",
        "552032534"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("far003/french-company-search-sirene").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": [
        "DANONE",
        "552032534",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("far003/french-company-search-sirene").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": [
    "DANONE",
    "552032534"
  ]
}' |
apify call far003/french-company-search-sirene --silent --output-dataset

```

## MCP server setup

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

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/NWPQOv2fypGnV7ysl/builds/yMK6C2G1TRZM1OVbG/openapi.json
