# France RGE Installers - Certified Renovation Firm Leads (`scrapersdelight/rge-installers-fr-scraper`) Actor

From $0.90 per 1,000 companies, no start fee. France's official ADEME RGE register: ~60,012 certified renovation firms with SIRET, email, phone, website, address, coordinates and every RGE qualification held. Deduplicated by SIRET. Filter by department, trade, certifier, keyword or radius.

- **URL**: https://apify.com/scrapersdelight/rge-installers-fr-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.90 / 1,000 per company row returneds

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

## 🇫🇷 France RGE Installers — Certified Renovation Firm Leads

**Every RGE-certified renovation company in France, from the government's own register — with the
email address.**

RGE (*Reconnu Garant de l'Environnement*) is the state certification a French firm must hold before
a homeowner can claim MaPrimeRénov' or CEE subsidies for the work. It is, in practice, the list of
every heat-pump installer, insulation contractor, solar fitter, window fitter and energy auditor in
France that is allowed to do subsidised work — published by ADEME under the Licence Ouverte.

This Actor turns it into a lead list: **one row per company**, not one row per certificate.

***

### 📊 The numbers, measured on 2026-09-16

Every figure below was counted against the live API on the day this Actor was built. Nothing here
is an estimate.

| | |
|---|---|
| Qualification rows in the register | **160,281** |
| Distinct companies (SIRET) | **~60,012** |
| Rows per company | **2.67** (one real firm holds 38 qualifications at once) |
| Register last rebuilt | daily — `dataUpdatedAt` was the capture day itself |
| Qualifications still valid today | **159,807 of 160,281 = 99.70%** |
| Full sweep | 161 pages, ~3 minutes |

#### Field fill — counted, not estimated

Two independent measurements, because a sample and a census answer slightly different questions.

**A. Census — the register's own counts over all 160,281 rows** (`?qs=email:*` etc., exact):

| Field | Rows | % of rows | Companies | % of companies |
|---|---|---|---|---|
| `email` | 158,399 | **98.83%** | ~59,581 | **99.28%** |
| `phone` | 154,194 | **96.20%** | ~57,616 | **96.01%** |
| `website` | 107,818 | **67.27%** | ~39,760 | **66.25%** |
| email **or** phone | 159,404 | **99.45%** | ~59,724 | **99.52%** |
| neither email, phone nor website | 66 | 0.04% | ~33 | 0.05% |

**B. Sample — 1,000 rows pulled in true random order (`sort=_rand`), collapsed to 992 companies,
recounted by `offline_validate.mjs` on every run:**

| Field | Fill | | Field | Fill |
|---|---|---|---|---|
| `siret` | 100.0% | | `email` | **98.8%** |
| `companyName` | 100.0% | | `phone` | **96.4%** |
| `streetAddress` | 100.0% | | `website` | **64.7%** |
| `postalCode` | 100.0% | | `latitude` / `longitude` | 100.0% |
| `city` | 100.0% | | `departmentCode` | 100.0% |
| `qualifications` | 100.0% | | `certifiers` | 99.9% |
| `firstQualifiedOn` / `qualifiedUntil` | 100.0% | | email **or** phone | **99.2%** |

"Filled" excludes `""`, `null`, `"N/A"`, `"-"`, `"Non renseigné"` and `"Inconnu"` — the register
uses the last two as real values, and counting them would overstate `organisme` and `domaine`.

**The emails are real, per-firm mailboxes.** 975 of the 980 addresses in the random sample are
distinct (99.5%), so this is not one certifier's inbox repeated 60,000 times. 51.5% of them are
free-mail domains (gmail 22.8%, orange 13.6%, wanadoo, hotmail, free…) — the expected signature of
French sole-trader tradesmen, not a data defect.

***

### 🧩 What one row is — read this before you compare row counts

**The register publishes one row per QUALIFICATION, not per company.** A firm certified for heat
pumps, wood stoves and condensing boilers appears three times. Across the whole register that is
**160,281 rows for ~60,012 companies**.

- **`oneRowPerCompany: true` (the default)** — one row per SIRET, with every qualification collapsed
  into a `qualifications` array plus `qualificationCount`, `workDomains`, `certifiers` and the
  overall validity window. **You are charged once per company.**
- **`oneRowPerCompany: false`** — the raw register grain, one row per qualification. Each row is
  charged, so the same company costs you about 2.67 times.

An actor that ships the raw rows and calls them "160,000 French renovation companies" is selling
you 60,000 companies counted 2.67 times. The deduplication here is a streaming group-by over a
SIRET-sorted cursor, so it is exact — a firm whose rows straddle a page boundary still comes out
once, with all of its qualifications.

***

### 📦 What you get per company

| Field | What it is |
|---|---|
| `siret` / `siren` | The French company identifier. `siren` is null when the register filed a placeholder. |
| `siretIsValid` | Luhn checksum on the SIRET. All 1,000 random-sample rows pass; the failures are the handful of foreign contractors. |
| `companyName` | Trading name as filed |
| `email` / `phone` / `website` | Contact details, normalised (email lower-cased and regex-checked, website given a scheme) |
| `streetAddress` / `postalCode` / `city` / `fullAddress` | Postal address |
| `departmentCode` | Derived: `75`, `69`, `971`… and `2A` / `2B` for Corsica |
| `latitude` / `longitude` | Geocoded by the register. **Null when absent — never a fabricated `0,0`.** |
| `qualificationCount` | How many RGE qualifications this firm holds |
| `workCategories` | `Travaux d'efficacité énergétique`, `Installations d'énergies renouvelables`, `Etudes énergétiques`, `Rénovation globale` |
| `workDomains` | The specific trades — heat pumps, external wall insulation, solar PV, wood boilers, energy audits… (31 values) |
| `certifiers` | `qualibat` (95,848 rows), `qualitenr` (43,643), `opqibi` (8,665), `cnoa` (7,849), `qualifelec` (3,552)… |
| `certificates` | `QUALIBAT-RGE`, `QualiPAC module Chauffage et ECS`, `Qualibois Eau`, `QualiPV 36`… |
| `worksForHomeowners` | The register's `particulier` flag — takes private-homeowner (MaPrimeRénov') work. 96.45% of rows. |
| `hasCurrentQualification` | Any qualification still valid today |
| `firstQualifiedOn` / `qualifiedUntil` | The firm's overall RGE window |
| `qualifications[]` | Every certificate: name, code, trade, category, certifier, validity dates, `isCurrent`, and the PDF `certificateUrl` |
| `registerUrl` | Link back to this firm's own rows in the live register |

***

### 🔎 How to filter

All filtering happens **at the source** — rows you filter out are never fetched, never delivered and
never charged.

- **`postalPrefixes`** — `["75"]` Paris, `["69"]` Rhône, `["971"]` Guadeloupe, `["75001"]` one exact
  postcode. Corsica has no numeric code, so `"2A"` and `"2B"` are expanded for you
  (2A = 200xx/201xx = 405 rows, 2B = 202xx–206xx = 461; together exactly the 866 rows of `20*`).
- **`workDomains` / `workCategories` / `certifiers` / `certificates`** — picked from the register's
  own vocabulary, read live from its facet endpoint.
- **`keyword`** — full-text across every field. `pompe a chaleur` matches 21,061 rows.
- **`centreLatitude` / `centreLongitude` / `radiusKm`** — radius search. 10 km of central Paris =
  6,271 rows; 10 km of central Lyon = 2,061.
- **`onlyCurrentQualifications`** (on by default) — drop lapsed certificates.
- **`worksForHomeownersOnly`** — only firms that take private-homeowner work.
- **`requireContact`** — only firms with an email or a phone, filtered at the source so you never
  pay for a row you cannot act on.

A worked example, measured live on 2026-09-16: Rhône + heat-pump installers + homeowner work +
currently valid + reachable → **408 qualification rows collapsing to 407 companies, 100% with an
email, 99.3% with a phone, 91.4% with a website** — one request, 0.43 s, $0.37.

***

### 💰 Pricing

**$0.0009 per row delivered — $0.90 per 1,000 companies. No run-start fee.**

| Scope | Rows | Cost |
|---|---|---|
| One department's heat-pump installers (Rhône, example above) | 407 | $0.37 |
| 1,000 companies (the default cap) | 1,000 | $0.90 |
| Every RGE company in France | ~60,012 | ~$54 |

You are charged for rows **delivered**. Rows excluded by your filters are never delivered and never
charged, a run that matches nothing costs nothing, and delivery and billing are the same call — so
a buyer who hits their charge cap stops receiving rows at exactly the point they stop paying for
them.

With `oneRowPerCompany` off you are charged per qualification row, which is ~2.67× more for the same
firms. The default is on.

***

### ⚖️ Source and legality

- **Source:** [`data.ademe.fr` — Liste des entreprises RGE](https://data.ademe.fr/datasets/liste-des-entreprises-rge-2),
  published by ADEME (the French Environment and Energy Management Agency), the register behind
  the official France Rénov' installer search.
- **Licence:** **Licence Ouverte / Open Licence (Etalab)** — explicitly permits reuse, including
  commercial reuse, with attribution.
- **robots.txt:** `data.ademe.fr/robots.txt` is `User-agent: * / Allow: /`.
- **No credentials, no login, no API key.** Nothing here is behind an authentication wall.
- This is business contact data published by companies for the purpose of being contacted about
  renovation work. It is not personal data about consumers. Under GDPR you still need a lawful basis
  for B2B prospecting and must honour opt-outs — that is your responsibility as the data controller.

***

### 🛠 How it works, and why it is built this way

**Cursor paging, never offsets.** The API hard-caps `size + skip` at 10,000 and answers past it with
an HTTP 400 whose body is **plain text**, not JSON — `"size + skip" cannot be more than 10000`. An
actor that `JSON.parse`s every response dies with a `SyntaxError` there. This one follows the
envelope's own `next` cursor, which is uncapped (12,000 rows walked across 12 pages, zero duplicate
row ids), and never builds an offset URL at all.

**The cursor is followed verbatim.** The server rewrites every `next` onto the numeric dataset id,
so reconstructing the URL from the slug silently restarts the walk at row 0.

**`total` appears on the first page only.** Every cursor page omits it. It is read once, used to
floor-assert the walk, and its absence is never read as zero.

**Filter values are matched as exact keywords, not as phrases.** The register really does publish
`Isolation par l'intérieur des murs ou rampants de toitures  ou plafonds` with two spaces — that
exact string matches 18,199 rows and the single-space version matches **zero**. A near-miss returns
an empty result that looks exactly like a genuine "no such firms". So every value you supply is
checked against the register's live facet list *before* the run starts, and a bad value fails the
run with the real list, having charged you nothing.

**A non-200 is a transport failure, not the end of the data.** Each page is retried on a fresh proxy
session; if it still fails the run stops and reports the exact unreachable URL. It never skips a
page and reports success, because a silently missing page in a register looks identical to a
register that is simply smaller than you thought.

**Short pages are caught.** The API responds gzipped and chunked with no `Content-Length`, so bytes
cannot be checked. Instead: a page shorter than requested *while still offering a next cursor* is
treated as a truncated body and fails the run. (A short page with no cursor is the normal last page
— measured on a 44-row query that ended 10/10/10/10/4.)

***

### ⚠️ Honest limits

- **This is a certification register, not a company database.** There is no revenue, headcount,
  legal form or director name here. Join on `siren` if you need those.
- **~33 companies in the whole register publish neither an email, a phone nor a website.** They are
  still delivered and charged (they have a SIRET, a name and an address). Set `requireContact: true`
  to exclude them at the source. The exact count for your run is in `RUN_SUMMARY`.
- **A firm's own rows occasionally disagree with each other.** On the captured pages the address
  differed across rows for 9 of 124 multi-row firms, the website for 8, the email for 2. The merge
  takes the most recently started qualification's values first and back-fills nulls from older rows.
  There is no way to know which one ADEME considers current.
- **`departmentCode` is derived from the postal code.** 20xxx covers both Corsican departments, so
  the 2A/2B split uses the standard 200/201 vs 202–206 rule. It is a postal split, not an INSEE
  lookup.
- **Coordinates are ADEME's, and they are not always right.** The register geocoded a Portuguese
  firm to a point near Paris. Where the register publishes no coordinate the field is `null`, never
  `0,0`.
- **A handful of foreign contractors sit at the top of an unfiltered run.** SIRET order puts the
  register's placeholder identifiers (`00000000…`, postal code `00000`) first. They are flagged
  `siretIsValid: false` and their `postalCode`, `departmentCode` and `siren` come back `null` rather
  than as fake values. Use a filter if you want a targeted slice rather than the first N.
- **The row count moves.** ADEME rebuilds the dataset daily; it was 161,428 in an older survey and
  160,281 on 2026-09-16. Nothing in this Actor hardcodes it.
- **Not tested through an Apify datacenter proxy.** The source has no anti-bot, no user-agent gate,
  no rate limit (20 concurrent requests → 20× HTTP 200) and `robots: Allow: /`, so an IP block is
  unlikely — but it was measured from a normal residential IP, not from Apify's pool. The proxy
  input is optional and the Actor runs fine with it switched off.

***

### ❓ FAQ

**What is RGE?**
*Reconnu Garant de l'Environnement* — the French state's certification for energy-renovation
contractors. A homeowner can only claim MaPrimeRénov' or CEE subsidies if the work is done by an
RGE-certified firm, so RGE status is effectively a licence to do subsidised renovation work.

**Who buys this list?**
Heat-pump, insulation, boiler and solar manufacturers recruiting installer partners; MaPrimeRénov'
lead networks and renovation marketplaces sourcing supply; tooling, materials and software vendors
selling into the trade; and anyone building a "find an RGE installer near me" product.

**Is this the same list as France Rénov'?**
Yes — France Rénov's public installer search is built on this ADEME register. This Actor reads the
register directly, so there is no scraping of the consumer-facing site.

**Why is `website` only ~65% filled?**
Because two-thirds of these firms have a website and a third do not. French renovation work is
dominated by sole traders and small *artisan* businesses that operate on a mobile number and a
Gmail address. That is the market, not a gap in the data — 99.5% of companies still have an email
or a phone.

**How do I get one department only?**
`postalPrefixes: ["31"]` for Haute-Garonne. Several at once are OR-ed: `["75", "92", "93", "94"]`.

**How do I get only firms whose certificate has not expired?**
`onlyCurrentQualifications` is on by default. 99.70% of the register is current, so this mostly
removes the 474 lapsed rows — which are exactly the firms you do not want to pitch as RGE partners.

**Can I get every RGE company in France in one run?**
Yes. Set `maxCompanies: 0`. That is ~60,012 companies, 161 pages and about three minutes.

**Does it handle a company that holds 38 certificates?**
Yes — that is a real firm in the register. It comes back as one row with `qualificationCount: 38`
and all 38 in the `qualifications` array, and is charged once.

**What is in `RUN_SUMMARY`?**
How many rows the register said matched, how many were actually read, how many companies were
delivered (= charged), the rows-per-company ratio actually observed for your filter, the count of
companies with no email and no phone, and the exact Lucene query that was run.

**Do I need a proxy?**
No. The source has no anti-bot and no rate limit. The proxy input is there if you want it.

# Actor input Schema

## `oneRowPerCompany` (type: `boolean`):

ON (recommended): one row per company, with all of its qualifications collapsed into a `qualifications` array plus `qualificationCount`. You are charged once per company. OFF: one row per qualification, the raw register grain, which means the same company appears about 2.67 times on average and is charged each time.

## `postalPrefixes` (type: `array`):

French department codes or postal prefixes, matched against the firm's postal code: "75" = Paris, "69" = Rhône, "971" = Guadeloupe, "75001" = one exact postal code. Use "2A" / "2B" for Corsica — both share the 20xxx range, so the actor expands them (2A = 200xx/201xx, 405 rows; 2B = 202xx-206xx, 461 rows). Multiple entries are OR-ed.

## `centreLatitude` (type: `string`):

Optional radius search instead of (or as well as) postal prefixes. Latitude of the centre point, e.g. 48.8566 for Paris. Needs all three radius fields.

## `centreLongitude` (type: `string`):

Longitude of the centre point, e.g. 2.3522 for Paris.

## `radiusKm` (type: `string`):

Radius in kilometres around the centre point. Measured: 10 km of central Paris = 6,271 qualification rows, 10 km of central Lyon = 2,061.

## `workCategories` (type: `array`):

Top-level category (`meta_domaine`). Leave empty for all.

## `workDomains` (type: `array`):

The specific trade (`domaine`) - heat pumps, external wall insulation, solar PV, wood stoves, energy audits and so on. Leave empty for all.

## `certifiers` (type: `array`):

The body that issued the RGE qualification (`organisme`). Leave empty for all.

## `certificates` (type: `array`):

Exact certificate label (`nom_certificat`), for the 43 values the register uses. The big ones are "QUALIBAT-RGE" (95 848 rows), "QualiPAC module Chauffage et ECS" (24 700 rows), "Certificat OPQIBI" (8 665 rows), "Tableau de l´Ordre" (7 849 rows), "Qualibois Eau" (6 208 rows), "Certificat Qualifelec RGE" (3 552 rows). Anything not in the live list fails the run before it charges you.

## `keyword` (type: `string`):

Full-text search across every field - company name, qualification, address. Measured: "pompe a chaleur" matches 21,061 qualification rows. Combined with the filters above using AND.

## `onlyCurrentQualifications` (type: `boolean`):

Keep only qualifications whose end date is today or later. Measured 2026-09-16: 159,807 of 160,281 rows (99.70%) are current, so this register is live rather than an archive - but the 0.30% that lapsed are exactly the firms you do not want to pitch as RGE partners.

## `worksForHomeownersOnly` (type: `boolean`):

The register's `particulier` flag - the firm takes private-homeowner work (MaPrimeRénov' / CEE jobs) rather than commercial or public contracts only. Measured: 154,590 of 160,281 rows (96.45%).

## `requireContact` (type: `boolean`):

Filter at the source so you never pay for a row you cannot act on. Measured: 159,404 of 160,281 rows (99.45%) carry an email or a phone; only ~33 companies in the whole register publish neither an email, a phone nor a website.

## `maxCompanies` (type: `integer`):

Hard cap on rows delivered - and therefore on rows charged. Counts companies when "one row per company" is on, qualifications when it is off. 0 means no cap: the whole register is ~60,012 companies / 160,281 qualification rows, about 3 minutes. A capped run walks the register in SIRET order, so it returns the numerically lowest SIRETs first - use the filters above when you want a specific slice rather than the first N.

## `proxyConfiguration` (type: `object`):

Optional. data.ademe.fr has no anti-bot, no user-agent gate and no rate limit (measured: 20 concurrent requests -> 20x HTTP 200), so datacenter is plenty and no proxy at all also works.

## Actor input object example

```json
{
  "oneRowPerCompany": true,
  "postalPrefixes": [],
  "workCategories": [],
  "workDomains": [],
  "certifiers": [],
  "certificates": [],
  "onlyCurrentQualifications": true,
  "worksForHomeownersOnly": false,
  "requireContact": false,
  "maxCompanies": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per RGE-certified company (or per qualification, if you turned deduplication off): SIRET, SIREN, name, email, phone, website, address, coordinates and every RGE qualification it holds with the certifier and validity window.

## `runSummary` (type: `string`):

RUN\_SUMMARY: matching rows in the register vs rows read, companies delivered and charged, rows-per-company actually observed, and how many companies publish no email and no phone.

# 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 = {
    "oneRowPerCompany": true,
    "onlyCurrentQualifications": true,
    "maxCompanies": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/rge-installers-fr-scraper").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 = {
    "oneRowPerCompany": True,
    "onlyCurrentQualifications": True,
    "maxCompanies": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/rge-installers-fr-scraper").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 '{
  "oneRowPerCompany": true,
  "onlyCurrentQualifications": true,
  "maxCompanies": 50
}' |
apify call scrapersdelight/rge-installers-fr-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/rge-installers-fr-scraper"
        }
    }
}
```

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/ga4qEDwRaQMdkp9WD/builds/gAyEqvfFWvNQOFkzn/openapi.json
