# Experts-Comptables Annuaire Scraper - French Accountant Leads (`scrapersdelight/experts-comptables-annuaire-scraper`) Actor

$1.80 per 1,000 cabinets, no start fee. Every cabinet on the Ordre des experts-comptables public annuaire: phone (on 92-98% of profiles, measured), website, street address, GPS, entity type, named experts-comptables. By commune, department, region or all France. Optional audited SIREN join.

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

## Pricing

from $1.80 / 1,000 per cabinet 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

## Experts-Comptables Annuaire Scraper — French accountant leads from the Ordre

Export **every accounting firm inscribed on the Tableau of the Ordre des experts-comptables** from its public
annuaire ([annuaire.experts-comptables.org](https://annuaire.experts-comptables.org/)) as a lead list:
**phone, website, street address, GPS, entity type, the named experts-comptables**, languages, opening hours
and social links — by **commune, department, region or all of France**. Optional **SIREN / SIRET join**
against the State's open business register, audited row by row.

**$1.80 per 1,000 cabinets. No start fee.** A cabinet that publishes neither a phone nor a website is
skipped and never charged.

### Who buys this

| You are | What you do with it |
|---|---|
| Accounting / fintech SaaS partner team (bookkeeping, e-invoicing, cash management) | Recruit cabinets into a partner or reseller programme, by region |
| Payroll / HR software, insurers, banks | Build a cabinet-partner channel; phone every firm in a department |
| B2B data teams | A clean national file keyed on the annuaire id, refreshed on demand |
| Market analysts | Count and map the profession by commune, department and region |

### What one row is

One row = **one cabinet** (the annuaire's own unit: a société d'expertise comptable, an expert-comptable
indépendant, an SPE, a branch or an AGC) with the experts-comptables it lists. Rows are deduplicated on the
annuaire's own id, so a cabinet is never delivered or charged twice.

#### Measured fill (contact fields first)

Measured on live runs on 2026-09-23 and on two opposite-biased sampling frames captured the day before.
Population numbers are quoted as a range across frames, never as one number.

| Field | Rhône (69), live run: 1,385 profiles read, 1,319 delivered | Frame A: 60 random national profiles | Frame B: every cabinet in Rodez + Ajaccio (98) |
|---|---|---|---|
| **phone** | **99.8% of delivered** (1,316 of all 1,385 profiles = 95.0%) | 91.7% | 98.0% |
| **phoneE164** | 99.8% (every printed phone reformatted) | — | — |
| **website** | 7.9% | 8.3% | 10.2% |
| linkedinUrl | 2.7% | 1.7% | 3.1% |
| facebookUrl | 1.2% | 1.7% | 2.0% |
| **email** | **0% — the annuaire does not publish email** (see below) | 0% | 0% |
| contactFormAvailable | 97.4% | — | — |
| streetAddress, postalCode, city | 100% | 100% | 100% |
| lat / lng | 100% | 100% | 100% |
| entityType | 100% | 100% of cards | 100% of cards |
| accountants (≥1 named) | 99.8% | 100% | 100% |
| languages | 99.5% | 100% | 100% |
| openingHours | 4.9% | 3.3% | 6.1% |
| logoUrl | 11.0% | 11.7% | 13.3% |

Between 2% and 8% of profiles (by frame; Rhône 5.0%) publish **no phone at all** (the JSON-LD `telephone` is empty *and* the page has no
"Appeler" button — the call modal comes back with `tel:` empty, measured). With **Only cabinets with a phone
or website** on (default) those are skipped and not charged; they are counted in `RUN_SUMMARY`.

**Email is not published.** The Ordre offers a "Contacter par mail" form instead of an address.
`email` is always `null` by design, and `contactFormAvailable` tells you whether the form exists. When a
cabinet publishes a website (~8–10%), `website` gives you the domain to chain into an imprint/contact
enricher. We never guess a domain or an address.

### Fields

| Field | Meaning |
|---|---|
| `annuaireId` | Numeric id from the profile URL — the stable key |
| `ordreFirmId` | 12-digit id the annuaire attaches to the cabinet's call/contact buttons, verbatim |
| `name` | Cabinet name as printed |
| `entityType` / `entityTypeLabel` | `firm` (Société d'expertise comptable), `independent` (Expert-comptable indépendant), `multiProfessionalFirm` (SPE), `branch` (Succursale), `managementAssociation` (AGC) — read from the label the annuaire prints on each result card, never inferred from the name |
| `phone` | As printed by the Ordre (schema.org `telephone`) |
| `phoneE164` | Pure reformat of `phone`: +33, or +590/+596/+594/+262 for the overseas departments. Never completed: a short or double number gives `null` |
| `website`, `linkedinUrl`, `facebookUrl`, `instagramUrl`, `twitterUrl`, `youtubeUrl` | Classified **by host** from the profile's "Nos sites" panel (the site labels Facebook pages "Site web", so its label is not trusted) |
| `links` | Every published link with the site's own label |
| `contactFormAvailable` | The "Contacter par mail" form exists |
| `email` | Always `null` (not published) |
| `streetAddress`, `postalCode`, `city`, `country` | Postal address as published (CEDEX codes kept as printed) |
| `departmentCode`, `departmentName`, `regionName`, `citySlug` | From the annuaire's own region → department → commune index (null in profile-URL mode) |
| `lat`, `lng` | Coordinates the annuaire publishes |
| `accountants` | `[{firstName, lastName, fullName}]` — the experts-comptables listed in the cabinet |
| `accountantCount` | Number of listed experts-comptables |
| `languages` | Languages spoken |
| `openingHours` | `[{day, opens, closes}]` from schema.org `openingHoursSpecification` (a split day gives two entries) |
| `logoUrl`, `tagline` | When the cabinet uploaded them |
| `siren`, `siret`, `sirenLegalName`, `sirenNafCode`, `sirenEstablishmentAddress`, `sirenMatchMethod`, `sirenMatchStatus`, `directors`, `sireneUrl` | Optional SIREN join, see below. `sirenMatchStatus` = `matched`, `no-match`, `ambiguous`, `error` or `not-requested` |
| `profileUrl`, `source`, `scrapedAt` | Provenance |

#### Real example row (Rhône run, 2026-09-23, accountants list shortened here)

```json
{
  "annuaireId": 7602,
  "ordreFirmId": "260000493901",
  "name": "FORVIS MAZARS",
  "entityType": "firm",
  "entityTypeLabel": "Société d'expertise comptable",
  "phone": "04 26 84 52 52",
  "phoneE164": "+33426845252",
  "website": "https://www.forvismazars.com/fr/fr",
  "linkedinUrl": "https://www.linkedin.com/company/forvis-mazars-en-france/",
  "facebookUrl": null,
  "twitterUrl": "https://x.com/forvismazarsfr",
  "youtubeUrl": "https://www.youtube.com/user/MazarsFrance",
  "contactFormAvailable": true,
  "email": null,
  "streetAddress": "109 RUE TETE D OR - CS 10363",
  "postalCode": "69451",
  "city": "Lyon",
  "country": "FR",
  "departmentCode": "69",
  "departmentName": "Rhône",
  "regionName": "Auvergne-Rhône-Alpes",
  "citySlug": "lyon",
  "lat": 45.764977,
  "lng": 4.853312,
  "accountants": [
    { "lastName": "BERTUCAT-LOUWAGIE", "firstName": "ALEXANDRA", "fullName": "ALEXANDRA BERTUCAT-LOUWAGIE" },
    { "lastName": "BLONDEAU", "firstName": "RENAUD", "fullName": "RENAUD BLONDEAU" }
  ],
  "accountantCount": 13,
  "languages": ["Français", "Anglais"],
  "openingHours": [
    { "day": "Lundi", "opens": "8:00", "closes": "12:00" },
    { "day": "Lundi", "opens": "14:00", "closes": "18:00" }
  ],
  "logoUrl": "https://annuaire.experts-comptables.org/upload/accounting_firm/cc6ede9222e948d4ffd98459648d485a.jpg",
  "tagline": null,
  "siren": null,
  "sirenMatchStatus": "not-requested",
  "profileUrl": "https://annuaire.experts-comptables.org/expert-comptable/7602-forvis-mazars-lyon-69451",
  "source": "annuaire.experts-comptables.org (Ordre des experts-comptables)",
  "scrapedAt": "2026-09-23T04:06:59.360Z"
}
```

### Input

| Input | Default | What it does |
|---|---|---|
| `cities` | — (prefill `Rodez (12)`) | Communes as the annuaire names them. `"Lyon (69)"` pins the department and skips a lookup; a bare name is matched in every department |
| `departments` | — | Codes such as `"69"`, `"75"`, `"971"`. Corsica is one department on the annuaire: `"20"` |
| `regions` | — | Any of the 18 regions the annuaire indexes |
| (all three empty) | | **All of France** — about 31,600 cabinets |
| `profileUrls` | — | Refresh known cabinets from their profile links; location inputs are ignored |
| `entityTypes` | all | Keep only firms, independents, SPE, branches and/or AGC. Filtered cabinets are never fetched or charged |
| `requireContact` | `true` | Skip (no charge) cabinets with neither a phone nor a website |
| `requirePhone` | `false` | Stricter: skip cabinets without a phone |
| `enrichSiren` | `false` | Add the audited SIREN/SIRET join |
| `maxResults` | `100` (prefill 20) | Stop after this many delivered rows; `0` = no limit |
| `maxConcurrency` | `8` | Requests in flight |
| `proxyConfiguration` | direct | See "How it works" |

API example:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~experts-comptables-annuaire-scraper/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "departments": ["69", "01"], "entityTypes": ["firm"], "maxResults": 0 }'
```

### Optional SIREN / SIRET join — audited, never guessed

The annuaire publishes **no SIREN**. With `enrichSiren` on, each row is looked up in the State's open
business-register API (recherche-entreprises.api.gouv.fr, SIRENE + RNE). A SIREN is written **only when
exactly one** active company with NAF **69.20Z** passes both gates:

1. **identity** — its legal name, full name or sigle equals the cabinet name after normalisation (accents,
   case, `&`/`et`, legal-form words, word order), or its shop-sign does (`sirenMatchMethod` then says
   `tradeName`); an entrepreneur individuel can only match an "Expert-comptable indépendant" listing;
2. **location** — one of its establishments has the annuaire's postal code, or the same commune (CEDEX).

Zero or several candidates → `siren: null` with the reason in `sirenMatchStatus`. There is **no director-based
bridge**: in testing, one expert-comptable directing two cabinets made such a bridge join the wrong company,
so it was removed. The register's own legal name and establishment address travel with every match so you
can audit it. Measured on departments 12 + 20 (366 cabinets, live run 2026-09-23): **237 matched (64.8%)** — firms 202 of
272 (74.3%), independents 35 of 72 (48.6%), AGC 0 of 21 — 129 no-match, 0 ambiguous, 0 errors; every match NAF
69.20Z and every SIRET distinct. Branches of one company correctly share a SIREN with different SIRETs.

A matched row is charged the `siren-matched` event ($0.0018); unmatched rows are not.

### Pricing

| Event | Price |
|---|---|
| `cabinet-scraped` — per cabinet row delivered | **$0.0018** ($1.80 / 1,000) |
| `siren-matched` — only with `enrichSiren`, per delivered row that carries a SIREN | **$0.0018** |
| Run start | $0 |

| Job | Rows | Cost |
|---|---|---|
| Try it (prefill: Rodez, 20 cabinets) | 20 | $0.036 |
| Rhône (69), all cabinets | ~1,320 | ~$2.37 |
| All of France | ~30,000 | ~$54 |
| All of France + SIREN (~65% match) | ~30,000 | ~$89 |

Never charged: duplicates, cabinets outside your entity-type filter, cabinets without phone or website (while
`requireContact` is on), profiles that no longer exist, pages that stayed unreachable. Set a maximum charge on
the run and the actor stops cleanly at it — rows it cannot pay for are neither delivered nor charged.

### How it works (and what it guarantees)

1. Reads the annuaire's own **region → department → commune index**, which prints the number of cabinets
   per commune (France printed **31,582** on 2026-09-22; region totals are checked against their department
   links on every run).
2. Walks each commune's result pages with a **pinned ordering seed**. Measured: without one the site
   reshuffles results between requests (the same page twice gives different cabinets), which silently
   duplicates and drops rows. The actor fails the run if the site stops honouring the seed.
3. **Floor-asserts every commune** against the count the annuaire prints (Rhône: 1,385 printed, 1,385 read,
   121 of 121 communes complete). A short commune is re-walked; a scope missing more than 1% fails the run
   instead of passing a partial file off as complete.
4. Fetches each cabinet's profile page (JSON-LD + HTML) and delivers the row.

**Transport.** The annuaire has no anti-bot. Requests go **direct** from the Apify container (measured
1,319 cabinets in 95 s); any request that fails direct — a 403/429/5xx or a 200 that is not the expected page
— is retried through **Apify RESIDENTIAL (France)** on a fresh session. Set a proxy to force every request
through it.

`RUN_SUMMARY` (key-value store) reports per-commune coverage against the printed counts, rows delivered ==
charged, the three kinds of nothing (profile gone / no contact published / unreachable), field fill on the
delivered rows, phones shared by several cabinets (network switchboards), SIREN statistics and transport.

### Data limits

- **No email** — not published by the Ordre (a contact form only).
- **Phone is the cabinet's published switchboard**, not a direct line. Some numbers are shared across the
  branches of one network (Rhône: the most-shared number sits on 13 cabinets); they are kept as printed and
  listed in `RUN_SUMMARY.phoneDistribution`.
- **Entity type, department and region come from search results**, so they are `null` in `profileUrls` mode.
- The `ordreFirmId` is the annuaire's own button id; it is **not** documented as the Tableau inscription
  number, so it is shipped verbatim and not relabelled.
- The national search lists 31,589 cabinets while the region index sums to 31,582: ~7 cabinets are reachable
  only by the national search and are not covered by the index walk.
- SIREN join: ~35% of cabinets get no SIREN (name differs from the register's, or the register address is in
  another commune); AGCs rarely match (they are associations, not NAF 69.20Z companies).

### Source

Data: the public annuaire of the **Conseil national de l'Ordre des experts-comptables**
([annuaire.experts-comptables.org](https://annuaire.experts-comptables.org/)). This actor is not affiliated
with the Ordre.

### FAQ

**How fresh is it?** Every run reads the live annuaire; nothing is cached.

**Why does a cabinet appear with a CEDEX postal code (e.g. 69451)?** That is what the cabinet declared;
it is kept as printed. `city` stays the commune name.

**Can I get all the experts-comptables as individual rows?** Each row carries the `accountants` array;
flatten it in your pipeline. The annuaire publishes no individual contact details for them.

# Actor input Schema

## `cities` (type: `array`):

Communes to export, as the annuaire names them. Add the department code in brackets to skip the lookup and to pick the right one of several same-named communes: "Rodez (12)", "Lyon (69)", "Saint-Denis (974)". A name without a code is matched in every department. Leave empty (and leave Regions/Departments empty) for all of France.

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

Department codes, every commune in each: "69", "75", "13", "971". Corsica is one department on the annuaire, code "20" (not 2A/2B).

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

Whole regions, every department and commune in each. Leave Communes, Departments and Regions all empty to export the whole of France (about 31,600 cabinets).

## `profileUrls` (type: `array`):

Optional. annuaire.experts-comptables.org profile links (https://annuaire.experts-comptables.org/expert-comptable/<id>-<slug>). When set, ONLY these profiles are fetched and the location fields above are ignored. Entity type, department and region are printed on search results, not on the profile page, so they are null in this mode.

## `entityTypes` (type: `array`):

Keep only these kinds of inscription, read from the type label the annuaire prints on each result card. Empty = all. Filtered cabinets are never fetched or charged.

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

On (default): a cabinet that publishes neither a phone nor a website is skipped and NOT charged (2-8% of profiles publish no phone, measured on three samples). Off: it is delivered and charged like any row.

## `requirePhone` (type: `boolean`):

Stricter: skip (and do not charge) any cabinet without a published phone number, even if it has a website.

## `enrichSiren` (type: `boolean`):

Off by default. On: each row is joined to the State's open business register (recherche-entreprises.api.gouv.fr). A SIREN is filled only when exactly one active NAF 69.20Z company matches the cabinet name (or its single listed expert-comptable as director) AND has an establishment at the same postal code or commune; otherwise it stays null with the reason in sirenMatchStatus. A matched row is charged the extra siren-matched event; unmatched rows are not. Slower: about 4 lookups per second.

## `maxResults` (type: `integer`):

Stop after this many delivered (and charged) cabinets. 0 = no limit (all of France is about 31,600 cabinets).

## `maxConcurrency` (type: `integer`):

Requests in flight at once. The annuaire has no anti-bot; 8 is fast and polite.

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

Default: direct (the annuaire has no anti-bot; measured 20x cheaper and faster than a proxy). Any request that fails direct is retried automatically through Apify RESIDENTIAL (France) on a fresh session. Set a proxy here only to force every request through it.

## Actor input object example

```json
{
  "cities": [
    "Rodez (12)"
  ],
  "requireContact": true,
  "requirePhone": false,
  "enrichSiren": false,
  "maxResults": 20,
  "maxConcurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per cabinet inscrit au Tableau: name, entity type, phone (+E.164), website and social links, street address, department and region, GPS, the named experts-comptables, languages, opening hours, and (optional) an audited SIREN/SIRET join.

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

RUN_SUMMARY: rows delivered == charged, per-commune coverage against the counts the annuaire prints, the three kinds of nothing (gone / no contact published / unreachable), field fill measured on the delivered rows, repeated-phone distribution and SIREN match statistics.

# 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 = {
    "cities": [
        "Rodez (12)"
    ],
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/experts-comptables-annuaire-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 = {
    "cities": ["Rodez (12)"],
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/experts-comptables-annuaire-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 '{
  "cities": [
    "Rodez (12)"
  ],
  "maxResults": 20
}' |
apify call scrapersdelight/experts-comptables-annuaire-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/experts-comptables-annuaire-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/yobkpHyAq1BFR1QOo/builds/eeDtkfEJD2aH8ZfbK/openapi.json
