Quick answer
France's official company registry is open, keyless and free — and it will tell you there are 10,000 matches, hand you 25 at a time, and then stop answering somewhere around page 400. That is not a bug and not rate limiting: page × per_page is hard-ceilinged at 10,000, so a broad query cannot be paged to the end no matter how patiently you ask. The France SIRENE Company Registry Scraper filters server-side to stay inside that ceiling and exports SIREN, legal form, address, workforce band and status at $4.20 per 1,000 companies.
The ceiling that makes broad queries a lie 🧱
Two numbers govern every run against recherche-entreprises.api.gouv.fr, and both were measured rather than assumed:
-
per_pagemaxes out at 25. Ask for more and you get 25. -
page × per_pageis capped at 10,000. Past that, paging ends.
The trap is that total_results cheerfully reports 10,000 for a broad filter — which is itself the display cap, not the real count of French companies matching your search. So a naive client reads "10,000 results", plans 400 pages, and produces a file it believes is complete. It is complete with respect to the cap, and silently truncated with respect to France.
The way out is not more paging, it is narrower queries. This Actor exposes the registry's real filters — free-text, NAF activity code, département, commune, workforce band, administrative status — and applies them server-side so each slice fits under the ceiling. It also stops cleanly and says so in the log when a query would cross page × per_page > 10,000, instead of walking off the end and reporting whatever it happened to collect as the answer.
The field name that is backwards from the one you'd guess 🔤
The workforce band arrives as tranche_effectif_salarie, a coded range, and the year that band refers to arrives as annee_tranche_effectif_salarie — year as a prefix. Every instinct says tranche_effectif_salarie_annee, suffix-style, matching the field it qualifies. It is not.
This one nearly shipped wrong here: the planning step guessed the suffix form, and it was only caught by checking the guess against a live response captured earlier that morning. A wrong key does not raise — dict.get() returns None, the column is present and empty, and you have a dataset where every company's workforce year is blank. Silent nulls again, which is the recurring theme of open-data work and the reason we ground field maps in captured payloads rather than in what the naming convention implies.
What "administrative status" actually means 💼
etat_administratif takes exactly two values: A (active) and C (ceased). It is the difference between a lead list and a list of companies that no longer exist, and the demo input ships with A set, because a lead-gen export defaulting to include dissolved companies is a footgun rather than a feature.
Underneath, the run behaves the way a paid Actor against a government endpoint should: 408/429/5xx are retried with capped exponential backoff, while a 400 is raised immediately with the API's own error text surfaced rather than retried five times — a malformed filter is not going to become well-formed on attempt four. A record that fails validation is logged and skipped, so one odd entry never sinks the page it arrived in. And input is validated before the run-start fee is charged, so a bad filter costs you nothing.
Is the SIRENE API hard to scrape? 🛡️
Not in the anti-bot sense, and we will not dress it up — it is a public French-government API with no challenge page, and we probed it before writing a client. The difficulty is the pagination ceiling that turns a broad query into a confident partial answer, and a field-naming convention that produces empty columns instead of errors. Both produce files that open cleanly and are wrong, which is the expensive kind.
FAQ
What is SIREN?
The 9-digit identifier France assigns to every registered company. It is the join key for essentially all French company data.
Can I pull every company in France?
Not in one query — the API's page × per_page ≤ 10,000 ceiling prevents it. Slice by département or NAF code and run the slices; the Actor stops cleanly at the ceiling rather than pretending.
Does it include closed companies?
Only if you ask. administrativeStatus defaults to A (active).
How is it billed?
$0.20 per run start plus $0.004 per company row, so 1,000 companies cost $4.20. Invalid input is rejected before the start fee is charged.
Built by Devil Scrapes. We publish the traps we hit, because the ones that return 200 OK are the expensive ones.
Top comments (0)