SIRENE company registry
Source module that extracts French companies from the local INSEE SIRENE registry, targeted by activity and geographic zone. France only.
Purpose
Source module: an alternative starting point to scrap that reads companies directly from the official French business registry (INSEE, base SIRENE), instead of Google Maps. Every record comes from a public administrative source, not from a live crawl: no browser, no proxy, no network request leaves the server, since the registry is mirrored locally and refreshed daily from the INSEE flux.
Use it when the request is "companies in this sector", "companies with this NAF code", "companies created recently", or any list that should not go through Google Maps at all.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
activites |
string[] |
yes | Activities targeted. Each entry is either a trade in plain language ("plombier", "boulangerie") or a direct NAF code ("43.22A", with or without the dot). A single trade can resolve to several NAF codes: a curated synonym table returns every matching code when one exists, otherwise the closest official label match is kept alone. Codes with no match (neither a valid direct code nor a recognised trade) are dropped with a warning rather than failing the job. |
zones |
string[] |
yes | Zones in France only, same grammar as scrap: a department (code or name), a region (expands internally into one lookup per department), a city with an optional radius ("Toulouse 20km"), a postal code, or "France" for the whole country. A zone that resolves to a foreign country is recognised and skipped with an explicit warning (this source does not cover other countries) rather than surfacing the generic "zone not recognised" error. |
etablissements |
enum: tous | sieges |
no, default tous |
sieges restricts the result to registered head offices only. |
cree_apres |
string (YYYY-MM-DD) |
no, default empty | Keeps only companies whose legal unit was registered on or after this date. An invalid format is ignored with a warning rather than failing the job. |
effectif_min |
string |
no, default empty | Minimum headcount band, kept and everything above it. Accepts either an INSEE code ("11") or the exact label used in the tranche_effectif output column ("10 à 19 salariés"). Unrecognised values are ignored with a warning. |
stop_at |
int |
no, default 5000 |
Maximum number of companies returned. See Limits. |
Request body (trade name, single department):
{
"activites": ["plombier"],
"zones": ["31"]
}
Request body (direct NAF code, several activities, headquarters only, region):
{
"activites": ["43.22A", "boulangerie"],
"zones": ["Bretagne"],
"etablissements": "sieges",
"cree_apres": "2024-01-01",
"stop_at": 20000
}
If "France" is present alongside other zones, it wins: the job searches the whole country and the other zones become redundant (a log line says so, nothing fails). A region is not kept as a single filter, it is expanded into one lookup per department it contains, logged individually, so progress is visible department by department.
Outputs
Result file: UTF-8 CSV, semicolon delimiter, BOM (Excel-safe). Same standard row shape as every other source module, plus columns specific to SIRENE appended at the end.
| Column | Type | Description |
|---|---|---|
nom |
string | Company name, resolved through a fixed cascade: establishment's signboard (enseigne), then establishment's usual trade name, then legal unit's registered name, then legal unit's usual trade name, then, for a sole trader with none of the above, the owner's own first name and surname. This is why some rows show a person's name instead of a business name: that is the company's official name for an individual entrepreneur, not a data gap. |
telephone |
string | Always empty. The official registry carries no contact information. |
adresse |
string | Establishment's postal address. |
site_web |
string | Always empty, same reason as telephone. |
email |
string | Always empty, same reason as telephone. |
lien_google_maps |
string | Always empty, kept only for column compatibility with the standard row shape shared across source modules; there is no Google Maps identity to link to here. |
note, nb_avis |
float, int | Always empty, same reason as lien_google_maps. |
query |
string | The exact activites entry (trade or NAF code) that produced this row, as typed by the caller: the way to tell rows apart when a job targets several activities at once. |
siren |
string | 9-digit SIREN of the legal unit. |
siret |
string | 14-digit SIRET of the establishment. |
code_postal |
string | Postal code. |
commune |
string | City name. |
naf |
string | NAF code of the establishment, base format ("43.22A"). |
naf_libelle |
string | Official INSEE label for that NAF code. |
tranche_effectif |
string | Human-readable headcount band (e.g. "10 à 19 salariés"), a label, never the raw INSEE band code. |
date_creation |
date | Registration date of the legal unit (YYYY-MM-DD). |
is_siege |
"oui" | "non" |
Whether this establishment is the registered head office. |
No phone, no email, no website: by design, not by failure. SIRENE is a legal registry, not a contact directory. Chain
legal_dataaftersireneto get named executives and financials: matching happens on the exact SIRET already present in every row, no name-based fuzzy matching involved.
Duplicates
A company is deduplicated on SIRET within the job: if a region expands into several department lookups, or an establishment would otherwise be counted twice, it is written once.
Lifecycle
Standard job lifecycle, see Jobs & lifecycle. No network access, no proxy, no browser: sirene reads a local SQLite database only, so it runs in the parallel pool rather than the shared multi-proxy queue and can execute alongside a scrap or any other multi-proxy job instead of waiting behind it. Progress is reported every 500 rows written. The output CSV is written even when the job ends with zero matches, for inspection.
Pipeline
| Field | Value |
|---|---|
needs |
null (source module, no input CSV; a root alongside scrap and import) |
produces |
pois |
Downstream chaining is column-driven, not just bucket-driven. The pipeline editor will happily connect sirene to any node accepting pois_any, but every enrichment except legal_data requires a column sirene never populates (site_web, lien_google_maps, telephone or email), and resolves to zero usable rows at run time instead of a design-time error.
legal_data: the module built for this chain. It needsnom(present) and matches by SIRET when available, giving an exact result: executives, capital, financials, BODACC signals.filter/sort: always compatible, work directly on the SIRENE columns (naf,tranche_effectif,date_creation,code_postal, etc).emails,socials,legal_ids,legal_mentions,techstack,ads_intelligence,brand_assets,dead_check,pricing,pagespeed(needsite_web) andreviews(needslien_google_maps): not meaningful directly aftersirene. Runlegal_dataor a website-discovery step first if a site is needed.
Endpoints
sirene is a pipeline root, the same way scrap and import are (see Pipeline orchestration). The full field-level contract, including config_schema and defaults, is served live at GET /api/pipelines/schema under nodes.sirene.
Pipeline node payload:
{
"type": "sirene",
"config": {
"activites": ["plombier"],
"zones": ["31"],
"etablissements": "tous",
"cree_apres": "",
"effectif_min": "",
"stop_at": 5000
}
}
Wire it into a graph and submit through the Pipelines API (POST /api/pipelines).
Direct run, without a pipeline
The module has its own creation route, to run it on its own without building a graph:
POST /api/jobs/sirene
{
"activites": ["plombier"],
"zones": ["31"],
"etablissements": "tous",
"cree_apres": "",
"effectif_min": "",
"stop_at": 5000
}
These are exactly the pipeline node's config fields, and exactly the body POST /api/sirene/estimate expects: the same object estimates the volume and then launches the extraction, with no transformation in between. The response is the standard job object. activites and zones are required, everything else is optional.
Two helper routes come with the module:
GET /api/sirene/activites?q=<text> find a NAF code from a trade name
POST /api/sirene/estimate expected volume before launching
Once a job exists, retrieval is standard regardless of how it was created:
GET /api/jobs/{job_id}
GET /api/jobs/{job_id}/download?format=csv|json|xlsx
Limits
Global quotas: see /docs/concepts/limits. Module-specific:
| Limit | Value |
|---|---|
Default result cap (stop_at) |
5000 |
Hard cap on stop_at |
50000. A request above it is never rejected: it is silently compacted to 50000 and a warning is logged. |
| Geographic coverage | France only. A foreign zone is skipped with a warning, not a job failure, as long as at least one French zone in the same request resolves. |
| Activity coverage | NAF rev.2 (2008), 732 sub-classes. Orders of magnitude measured on 2026-09-09: activites: ["43.22A"] (plumbers) restricted to department 31 (Haute-Garonne) matches a little over 1,200 establishments; activites: ["10.71C"] (bakeries) restricted to department 75 (Paris) matches a little over 1,800. These volumes move continuously: the daily INSEE feed opens and closes establishments every day, so an exact count is only true on the day it is taken. |
Errors
| Scenario | Behaviour |
|---|---|
activites empty |
Job fails immediately: no activity given. |
Every entry in activites unrecognised (neither a valid NAF code nor a known trade) |
Job fails: no usable NAF code could be determined. |
zones empty |
Job fails immediately: no zone given. |
Every zone in zones invalid, unrecognised, or foreign |
Job fails: no valid French zone resolved. |
| One zone among several is foreign or unrecognised | That zone is skipped with a warning; the job continues on the remaining zones. |
Invalid cree_apres format |
The date filter is ignored with a warning; the job continues without it. |
Unrecognised effectif_min |
The headcount filter is ignored with a warning; the job continues without it. |
stop_at not a number, zero, or negative |
Falls back to the default (5000) with a warning. |
stop_at above 50000 |
Compacted to 50000 with a warning; the job is never refused for asking too much. |
| Zero establishments match the resolved filters | The job fails with an explanation once resolution completes, after writing the (empty) CSV. Broaden the activity or the zone. |
What's next
legal_data: the natural next step, named executives, capital, financials, and lead status, matched exactly by SIRET.filter: narrow bynaf,tranche_effectif,date_creation, orcode_postalbefore paying for downstream enrichment.scrap: the Google Maps alternative, when contact details (phone, website) matter more than official registry accuracy.