# Doctolib France Scraper | Doctors & Clinics (`datascraperes/doctolib-france-scraper`) Actor

Find doctors, dentists and clinics on Doctolib France by specialty and city. Collect public profiles, practice addresses, phone numbers where available and visit motives. Export results to CSV, Excel or JSON, with optional region enrichment.

- **URL**: https://apify.com/datascraperes/doctolib-france-scraper.md
- **Developed by:** [DataScraperES](https://apify.com/datascraperes) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.75 / 1,000 doctolib public results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

Collect public Doctolib France provider profiles and search results in a structured Dataset. Start with the prefilled specialty and city search, or add public profile slugs for detailed provider records. Optional profile localization and French postal-region enrichment are available. **Each delivered record keeps its source identity and completeness status.**

### What this Actor does

This Actor collects publicly visible Doctolib France data without an account. It supports direct public profile slugs and bounded provider searches, then normalizes the returned profile, practitioner, practice, visit-motive, public contact, booking, location, and search-result fields into Dataset records. Direct profile details can request French, English, German, or Italian source localization; the source may translate only some fields.

Optional region enrichment adds a public French administrative region to practices and search-result provider locations with valid postal codes, using one cached lookup per unique code during the run. It does not access private patient information, authenticated areas, or booking actions.

### Use cases

- Build a structured directory of public healthcare providers in France.
- Enrich local healthcare research with specialties, practices, locations, and public booking indicators.
- Compare public provider search coverage across French localities.

### How to use

1. Open the Actor in Apify Console.
2. Add public profile slugs, or fill in **Specialties or keywords** and **Cities in France** using the normal form fields.
3. Click **Start** and open the Dataset when the run finishes.

### Input

Use `profileSlugs` for one or more public Doctolib profile identifiers, such as `laurent-martini-paris`. For searches, add one specialty such as `dentiste` or `cardiologue` per item in `searchKeywords`, and one complete French city name such as `Paris` or `Lyon` per item in `searchCities`. Every keyword is searched in every city; two keywords and two cities create four searches. Fill in both search lists to search for providers. Leave both lists empty when collecting only the profiles you have entered.

When several cities share a name, add the department code, for example `Saint-Denis, 93`. Enter city names rather than postal codes or districts. If a city cannot be identified, the run explains what to correct. An unsuccessful lookup does not create a result charge.

You can enter familiar French specialty names such as `Médecin généraliste`, `Pédiatre`, or `DENTISTE`; accents, capitalization, and extra spaces are accepted. Repeated terms or cities that differ only in capitalization and spacing do not create extra searches. The prefilled form searches for dentists in Paris and collects up to three results. Profile identifiers are optional.

`locale` selects French (`fr`), English (`en`), German (`de`), or Italian (`it`) for details from the profile list. Some fields may remain in their original language, and this setting does not translate search results. Enabling `includeRegion` adds the French administrative region to eligible locations. If the region is unavailable, the provider details remain available and the affected result is marked partial.

The three result limits work together. Each specialty/city search stops at its page or result limit, and the entire run stops at the overall limit. Increase the overall limit above the three-result example when you want a larger collection. These are the defaults used when a setting is omitted:

| Setting | What it controls | Default | Allowed range |
|---|---|---|---|
| `maxPagesPerSearch` | Pages checked for each specialty/city search | 5 | 1–50 |
| `maxResultsPerSearch` | Provider results for each specialty/city search | 100 | 1–1,000 |
| `maxTotalRecords` | Results across all profiles and searches | 100 | 1–5,000 |
| `locale` | Language requested for profile details | `fr` | `fr`, `en`, `de`, `it` |
| `includeRegion` | Add the French administrative region | `false` | On or off |
| `continueOnError` | Continue with remaining profiles and searches after a lookup issue | `true` | On or off |

With `continueOnError` disabled, collection stops after the first lookup issue. Results already collected remain available. Invalid input produces a partial summary explaining what to correct; collection does not start until the input is corrected.

The supported limits are 50 profile slugs, 20 keyword/city combinations, 50 pages per search, 1,000 results per search, and 5,000 total Dataset records per run.

The following example collects one specific profile, requests English where available, and includes its administrative region:

```json
{
  "profileSlugs": [
    "laurent-martini-paris"
  ],
  "locale": "en",
  "includeRegion": true,
  "maxPagesPerSearch": 1,
  "maxResultsPerSearch": 20,
  "maxTotalRecords": 1,
  "continueOnError": true,
  "searchKeywords": [],
  "searchCities": []
}
```

### Output

The Dataset contains one record per delivered public profile or search result. Profile records include normalized identity, specialty, practitioners, agendas, visit motives, practices, public contact fields, and booking indicators. Search-result records include the keyword, locality, page, source total, provider identity, specialty, location, public booking indicators, and public profile link.

Each record includes `recordKey`, `sourceType`, `sourceInput`, `sourceUrl`, `retrievedAt`, `sourceStatus`, `isPartial`, `partialReason`, and `partialReasons`. Profile records expose the requested `profile.locale`; when region enrichment is enabled, each eligible practice and search-result provider location exposes `region.value` and `region.label`. Use `recordKey` to deduplicate exports. The run summary and source-error list are available in the default Key-Value Store. A partial run keeps successfully collected records and explains the limitation in the summary; an empty or failed source does not become a successful record.

If some requested data cannot be collected, the run summary is `partial`, including when no results are available. Open **Run summary** to see how many results were collected, and **Collection issues** for the affected profiles or searches and any suggested corrections. Incorrect input also produces `partial` with an explanation; no collection starts and no results are charged. A failure to save results or the required summary can still fail the run.

The following is a complete profile item from the same one-profile input shown above:

```json
{
  "recordType": "profile",
  "schemaVersion": 1,
  "recordKey": "profile:laurent-martini-paris",
  "sourceType": "profile",
  "sourceInput": "laurent-martini-paris",
  "sourceUrl": "https://www.doctolib.fr/medecin-generaliste/levallois-perret",
  "retrievedAt": "2026-10-23T10:36:04.670999+00:00",
  "sourceStatus": "success",
  "isPartial": false,
  "partialReason": null,
  "partialReasons": [],
  "profile": {
    "locale": "en",
    "slug": "laurent-martini-paris",
    "profile": {
      "id": "382290",
      "nameWithTitle": "Dr. Laurent Martini",
      "nameWithTitleAndDeterminer": "Dr Laurent Martini",
      "subtitle": "General Practitioner",
      "pictureReference": "u4cfeuonrt46byrjbhy2",
      "speciality": {
        "id": "2",
        "kind": "medical",
        "slug": "medecin-generaliste",
        "name": "General medicine"
      },
      "organization": false,
      "organizationStatusId": null,
      "redirectUrl": "https://www.doctolib.fr/medecin-generaliste/levallois-perret",
      "placeSlug": "levallois-perret",
      "bookableOnRequestEnabled": false,
      "selfPayerBookingEnabled": false,
      "telehealthEnabled": false,
      "isSpecialized": false,
      "ineligibleToMps": false,
      "country": "fr"
    },
    "practitioners": [
      {
        "id": "47192345",
        "profileId": "382290",
        "name": "Martini",
        "nameWithTitle": "Dr. Laurent Martini",
        "nameWithTitleAndDeterminer": "Dr Laurent Martini",
        "speciality": "General Practitioner",
        "cloudinaryPublicId": "u4cfeuonrt46byrjbhy2"
      }
    ],
    "agendas": [
      {
        "id": "2531253",
        "practiceId": "600183",
        "practitionerId": "47192345",
        "organizationId": "299750",
        "patientBaseId": "470430",
        "specialityId": "2",
        "anonymous": false,
        "bookingTemporaryDisabled": false,
        "hidePractitioner": false,
        "landlineNumber": "01 89 40 45 60",
        "visitMotiveIds": [
          "12125134",
          "13821638",
          "12125133",
          "12125137",
          "15033010",
          "15033012",
          "15033015",
          "15895127",
          "15895124",
          "12125136",
          "15033019",
          "15033028",
          "15033137",
          "15033140",
          "13404301",
          "13404381",
          "15033025"
        ],
        "visitMotiveIdsByPracticeId": {
          "600183": [
            "12125134",
            "13821638",
            "12125133",
            "12125137",
            "15033010",
            "15033012",
            "15033015",
            "15895127",
            "15895124",
            "12125136",
            "15033019",
            "15033028",
            "15033137",
            "15033140",
            "13404301",
            "13404381",
            "15033025"
          ]
        }
      }
    ],
    "visitMotives": [
      {
        "id": "12125134",
        "name": "Initial general medicine visit",
        "position": 1,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490497",
        "telehealth": false
      },
      {
        "id": "13821638",
        "name": "General medicine visit",
        "position": 2,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490497",
        "telehealth": false
      },
      {
        "id": "12125133",
        "name": "General medicine follow-up visit",
        "position": 3,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490497",
        "telehealth": false
      },
      {
        "id": "12125137",
        "name": "Emergency",
        "position": 7,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490497",
        "telehealth": false
      },
      {
        "id": "15033010",
        "name": "Pose d'attelle cheville (fourni sur place)",
        "position": 11,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490497",
        "telehealth": false
      },
      {
        "id": "15033012",
        "name": "pose d'attelle genou (fourni sur place)",
        "position": 12,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490497",
        "telehealth": false
      },
      {
        "id": "15033015",
        "name": "pose d'attelle poignet/main/doigt (fourni sur place)",
        "position": 13,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490497",
        "telehealth": false
      },
      {
        "id": "15895127",
        "name": "Bilan de prévention",
        "position": null,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490497",
        "telehealth": false
      },
      {
        "id": "15895124",
        "name": "Bilan de santé ",
        "position": null,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490497",
        "telehealth": false
      },
      {
        "id": "12125136",
        "name": "Pediatric visit",
        "position": 4,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490498",
        "telehealth": false
      },
      {
        "id": "15033019",
        "name": "Consultation de pédiatrie à partir de 2 ans ",
        "position": 5,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490498",
        "telehealth": false
      },
      {
        "id": "15033028",
        "name": "Child - Half-year visit from 2 to 6 years",
        "position": 6,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490498",
        "telehealth": false
      },
      {
        "id": "15033137",
        "name": "Pose d'attelle cheville enfant (fourni sur place)",
        "position": null,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490498",
        "telehealth": false
      },
      {
        "id": "15033140",
        "name": "Urgence enfant",
        "position": null,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490498",
        "telehealth": false
      },
      {
        "id": "13404301",
        "name": "Dépistage COVID-19 test antigénique / TEST GRIPPE",
        "position": 8,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490504",
        "telehealth": false
      },
      {
        "id": "13404381",
        "name": "Dépistage apnée du sommeil",
        "position": 9,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490504",
        "telehealth": false
      },
      {
        "id": "15033025",
        "name": "Follow-up visit for sleep apnea",
        "position": 10,
        "specialityId": "2",
        "organizationId": "299750",
        "visitMotiveCategoryId": "490504",
        "telehealth": false
      }
    ],
    "places": [
      {
        "id": "practice-600183",
        "name": "Centre médical de Levallois ",
        "city": "Levallois-Perret",
        "zipcode": "92300",
        "latitude": 48.8905641,
        "longitude": 2.2819722,
        "address": "5 Rue Barbès",
        "practiceIds": [
          "600183"
        ],
        "slug": "levallois-perret",
        "landlineNumber": "01 89 40 45 60",
        "region": {
          "value": "ile-de-france",
          "label": "Ile De France"
        }
      },
      {
        "id": "practice-804836",
        "name": "CDS de Clichy",
        "city": "Clichy",
        "zipcode": "92110",
        "latitude": 48.9009013,
        "longitude": 2.3052896,
        "address": "63 Boulevard Jean Jaurès",
        "practiceIds": [
          "804836"
        ],
        "slug": "clichy",
        "landlineNumber": "01 89 40 70 20",
        "region": {
          "value": "ile-de-france",
          "label": "Ile De France"
        }
      }
    ]
  },
  "searchResult": null
}
```

### Pricing

You are charged for each public profile or provider-search result delivered to the Dataset. Unsuccessful lookups, empty searches, and run summaries do not create a result charge. The base price is **$1 per 1,000 results**. Apify applies your account tier automatically; the actual charge is per delivered result.

| Account tier | Price per result | Equivalent per 1,000 results |
|---|---:|---:|
| FREE | $0.001 | $1.00 |
| BRONZE | $0.0009 | $0.90 |
| SILVER | $0.0008 | $0.80 |
| GOLD | $0.00075 | $0.75 |
| PLATINUM | $0.00075 | $0.75 |
| DIAMOND | $0.00075 | $0.75 |

Check the Actor's **Pricing** tab for the current terms.

### Related Actors

| Actor | Best for |
|---|---|
| [Booksy Business Data Scraper](https://apify.com/datascraperes/booksy-scraper) | Public business listings and detailed profiles across Booksy markets. |
| [Google Maps Business Scraper | $1 per 1,000](https://apify.com/datascraperes/actor-google-maps) | Broader local-business discovery, contact fields, ratings, and map context. |
| [International Postal Address Parser & Normalizer](https://apify.com/datascraperes/international-address-parser) | Normalize addresses from provider or practice exports before matching and enrichment. |

### Limits and data quality

Coverage is limited to public Doctolib France data returned for the supplied profiles and France locality searches. Appointment availability and slots are not collected. Public booking indicators describe provider settings and do not confirm that an appointment is available. Provider listings can change after a run.

Searches may include providers in nearby towns. A valid search can return no matches; try a different specialty or city if this happens. If a city cannot be identified, **Collection issues** explains what to correct. With `continueOnError` enabled, other searches continue. If a lookup issue prevents results from being collected, the run summary is marked `partial`, including when zero results are available.

Localization is applied only to direct profile details and may be incomplete by field. Region enrichment is limited to valid five-digit French postal codes and is an optional additional source lookup; unavailable enrichment is reported without removing the main record.

Searches are intentionally bounded by page, result, and total-record limits. A run can finish with `partial` status when a page bound, per-search result bound, global record bound, or source limitation is reached; the run summary identifies the condition. Inspect the summary and keep records whose `sourceStatus` is `success` or `partial` according to your workflow. Use stable `recordKey` values when merging repeated exports.

### Frequently asked questions

#### Does this Actor require a Doctolib account?

No. It is designed for publicly visible France records and does not accept or request account credentials.

#### Does it book appointments?

No. It only reads public profile, search, and practice information. It never submits a booking or changes provider data.

#### Does `locale` translate every field?

No. It requests a supported source language for direct profile details, but the public response can retain source-language text for fields without a translation.

#### What does region enrichment add?

When enabled, it adds a broad French administrative region to eligible practices and search-result provider locations. It does not replace the original postal code, city, or address.

#### Can I collect all countries or private patient data?

No. This Actor is limited to public Doctolib France records. It is not a patient-data or authenticated-area collector.

### Responsible use

Use the Actor only for lawful, public-data purposes. You are responsible for complying with applicable privacy, data-protection, healthcare, copyright, and source terms, and for respecting any restrictions that apply to your intended use of provider information.

### Support

If a run does not produce the expected public records, open an issue in the Actor's **Issues** tab and include a reproducible input and the Apify run ID. Do not include credentials, private patient information, or other sensitive data in a support request.

# Actor input Schema

## `profileSlugs` (type: `array`):

Add one public Doctolib France profile identifier per item, for example laurent-martini-paris. Up to 50 profiles are supported; repeated identifiers are collected once. Leave this list empty to search by specialty and city. To collect only these profiles, clear both search lists.

## `searchKeywords` (type: `array`):

Add one French specialty or keyword per item, for example dentiste, Médecin généraliste, or Pédiatre. Accents, capitalization and extra spaces are accepted. Each term is searched in every city below: two terms and three cities create six searches. Enter both a specialty and a city, with at most 20 combinations.

## `searchCities` (type: `array`):

Add one complete French city name per item, for example Paris, Lyon, or Marseille. If several places share a name, include the department code, for example Saint-Denis, 93. Use city names rather than postal codes or districts. Results may include nearby providers. Each city is searched for every specialty above.

## `locale` (type: `string`):

Choose the language for details collected from the optional profile list: French, English, German, or Italian. Some information may remain in its original language. This setting does not translate specialty searches. The default is French.

## `includeRegion` (type: `boolean`):

Add the administrative region, such as Ile De France, to provider locations with a valid French postal code. This is optional and does not add a separate result charge. If the region is unavailable, the main provider details are kept and the result is marked partial.

## `maxPagesPerSearch` (type: `integer`):

Set how many pages to check for each specialty and city combination, from 1 to 50. Increase this to look for more providers. A search stops sooner if it reaches its result limit or the overall limit. If more results may remain, the run summary is marked partial.

## `maxResultsPerSearch` (type: `integer`):

Set the maximum number of provider results for each specialty and city combination, from 1 to 1,000. The page limit and overall result limit also apply. Fewer providers may be returned when there are not enough matches.

## `maxTotalRecords` (type: `integer`):

Set the overall result limit, from 1 to 5,000. This includes all profiles and specialty searches together. The prefilled example collects up to 3 results; increase it for a larger collection. Failed lookups and run summaries do not count as results.

## `continueOnError` (type: `boolean`):

When enabled, continue with the remaining profiles and searches if one cannot be collected. When disabled, stop after the first lookup issue. Already collected results remain available in either case, and the run summary is marked partial with an explanation.

## Actor input object example

```json
{
  "profileSlugs": [],
  "searchKeywords": [
    "dentiste"
  ],
  "searchCities": [
    "Paris"
  ],
  "locale": "fr",
  "includeRegion": false,
  "maxPagesPerSearch": 1,
  "maxResultsPerSearch": 20,
  "maxTotalRecords": 3,
  "continueOnError": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

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

No description

## `errors` (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 = {
    "profileSlugs": [],
    "searchKeywords": [
        "dentiste"
    ],
    "searchCities": [
        "Paris"
    ],
    "locale": "fr",
    "includeRegion": false,
    "maxPagesPerSearch": 1,
    "maxResultsPerSearch": 20,
    "maxTotalRecords": 3,
    "continueOnError": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("datascraperes/doctolib-france-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 = {
    "profileSlugs": [],
    "searchKeywords": ["dentiste"],
    "searchCities": ["Paris"],
    "locale": "fr",
    "includeRegion": False,
    "maxPagesPerSearch": 1,
    "maxResultsPerSearch": 20,
    "maxTotalRecords": 3,
    "continueOnError": True,
}

# Run the Actor and wait for it to finish
run = client.actor("datascraperes/doctolib-france-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 '{
  "profileSlugs": [],
  "searchKeywords": [
    "dentiste"
  ],
  "searchCities": [
    "Paris"
  ],
  "locale": "fr",
  "includeRegion": false,
  "maxPagesPerSearch": 1,
  "maxResultsPerSearch": 20,
  "maxTotalRecords": 3,
  "continueOnError": true
}' |
apify call datascraperes/doctolib-france-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datascraperes/doctolib-france-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/3TcLHCbG2rT4Iy3UD/builds/EgT7vbzAG5ZVZ1VpI/openapi.json
