# Ontario Dentist Register Scraper (RCDSO) (`crawlerbros/ontario-dentist-register-scraper`) Actor

Scrape RCDSO's public register of Ontario dentists (rcdso.org). Search by name, city, specialty, district, practice, or registration number for full profiles, practice locations, certificates and public discipline records. Also search Sedation/CT Scanner facility permits.

- **URL**: https://apify.com/crawlerbros/ontario-dentist-register-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Lead generation, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Ontario Dentist Register Scraper (RCDSO)

Scrape the official public register of licensed dentists in Ontario, Canada, sourced directly from the **Royal College of Dental Surgeons of Ontario (RCDSO)** — the provincial regulator for the dental profession. Search by name, city, specialty, designated electoral district, practice, or public concern/discipline category, or look up exact registration numbers, to get full dentist profiles: registration status, practice locations, academic history, certificates of registration, and public complaint/discipline case files. Also search the companion register of dental facilities holding a Sedation & Anesthesia or CT Scanner permit. No login, no API key, no proxy required.

### What this actor does

- **Three modes:**
  - `search` — search the dentist register by name, city, specialty, district, practice name/address/phone, or public concern category
  - `byRegistrationNumbers` — look up exact RCDSO registration numbers directly
  - `facilitySearch` — search dental facilities holding a Sedation & Anesthesia (SAFP) or CT Scanner (DCTS) permit
- **Full public profile per dentist:** registration status, specialty, designated electoral district, practice location(s), Sedation & Anesthesia / CT Scanner facility permit flags, academic history (degree year + institution), current and previous certificates of registration, and public complaint/discipline case files (case number, category, decision date)
- **Facility permits:** for each dental facility, the permit holder dentist, permit status, permit sub-type, and (for Sedation & Anesthesia) the highest level of sedation administered
- **Fast mode toggle:** disable full-profile enrichment to quickly browse large result sets with just name/status/address
- **Client-side quick filters:** "Only Members", "Has Conditions/Concerns", "Not Entitled to Practice", and name sort (mirrors RCDSO's own site controls)
- **Empty fields are omitted** — every record only contains what RCDSO actually publishes for that dentist/facility

### Output per dentist (`recordType: "dentist"`)

| Field | Description |
|---|---|
| `registrationNumber` | RCDSO registration number |
| `fullName` | Full legal name on file |
| `district` | One of RCDSO's 12 designated electoral districts |
| `specialties[]` | Specialty certificate(s) held, e.g. `Orthodontist` |
| `currentStatus` | e.g. `Member`, `Resigned`, `Retired`, `Deceased`, `Not Entitled to Practice` |
| `currentStatusNote` | RCDSO's explanatory text for the status, when published |
| `hasConcerns` | `true` if the dentist has any public concern/complaint/discipline record |
| `concernCategories[]` | Categories of public concern on file, e.g. `Discipline Results` |
| `disciplinaryCases[]` | `{ category, caseFile, decisionDate }` for each public case file |
| `academicRecords[]` | `{ credential, year, institution }`, e.g. Dental Degree / Specialty Training |
| `currentCertificates[]` | `{ certificateType, issuanceDate }` |
| `previousCertificates[]` | `{ certificateType, dateFrom, dateTo }` |
| `initialRegistrationDate` | ISO date of first RCDSO registration |
| `primaryPracticeName` | Practice/clinic name, when named |
| `primaryPracticeAddress`, `primaryPracticeCity`, `primaryPracticeProvince`, `primaryPracticePostalCode` | Primary practice address |
| `primaryPracticePhone` | Primary practice phone number |
| `sedationAnesthesiaFacilityPermit`, `ctScannerFacilityPermit` | Whether the primary practice holds these facility permits |
| `additionalPracticeLocations[]` | Other known practice/corporation addresses, when the dentist has more than one |
| `professionalCorporations[]` | `{ corporationName, address, city, province, postalCode, phone, certificateOfAuthorizationStatus, certificateOfAuthorizationIssuanceDate, shareholders[] }` for each incorporated professional corporation the dentist is a shareholder of; `shareholders[]` is `{ name, registrationNumber }` for every co-shareholder dentist |
| `sourceUrl` | Canonical RCDSO profile page |
| `recordType` | Always `dentist` |
| `scrapedAt` | UTC timestamp of the scrape |

Fields other than `registrationNumber`, `fullName`, `currentStatus`, `hasConcerns`, `sourceUrl`, `recordType` and `scrapedAt` are only present when `enrichWithFullProfile` is enabled (default) and RCDSO publishes that field for the dentist.

### Output per dental facility (`recordType: "facility"`)

| Field | Description |
|---|---|
| `facilityId` | RCDSO internal facility ID |
| `facilityName` | Facility/clinic name |
| `address`, `city`, `province`, `postalCode` | Facility address |
| `phone` | Facility phone number |
| `sedationPermitHolderName`, `sedationPermitHolderRegistrationNumber`, `sedationPermitHolderUrl` | The dentist holding the Sedation & Anesthesia permit |
| `sedationPermitStatus`, `sedationPermitType` | Permit status and sub-type (Type A / Type B / Type A and B) |
| `highestSedationLevel` | Highest level of sedation/anesthesia administered on-site |
| `ctScannerPermitHolderName`, `ctScannerPermitHolderRegistrationNumber`, `ctScannerPermitHolderUrl` | The dentist holding the CT Scanner permit |
| `ctScannerPermitStatus`, `ctScannerPermitType` | Permit status and scanner type (Craniofacial / Dentoalveolar) |
| `sourceUrl` | Canonical RCDSO facility page |
| `recordType` | Always `facility` |
| `scrapedAt` | UTC timestamp of the scrape |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | select | `search` | `search`, `byRegistrationNumbers`, or `facilitySearch` |
| `dentistName` | string | – | (mode=search) Name, e.g. "Smith" |
| `city` | string | – | (mode=search, facilitySearch) Practice/facility city |
| `specialty` | select | – | (mode=search) One of 11 RCDSO specialty certificates |
| `district` | select | – | (mode=search) One of 12 designated electoral districts |
| `concernType` | select | – | (mode=search) One of 11 public concern/discipline categories |
| `practiceName` | string | – | (mode=search) Practice/clinic name |
| `registrationNumber` | string | – | (mode=search) Narrow to one exact registration number |
| `address` | string | – | (mode=search, facilitySearch) Street address |
| `phone` | string | – | (mode=search, facilitySearch) Phone number |
| `resultFilter` | select | – | (mode=search) `onlyMembers`, `hasConditions`, or `notEntitled` |
| `sortBy` | select | `nameAsc` | (mode=search) `nameAsc` or `nameDesc` |
| `registrationNumbers` | array | – | (mode=byRegistrationNumbers) One or more exact registration numbers |
| `facilityType` | select | – | (mode=facilitySearch) `SAFP` (Sedation & Anesthesia) or `DCTS` (CT Scanner) |
| `facilityName` | string | – | (mode=facilitySearch) Facility/clinic name |
| `permitType` | select | – | (mode=facilitySearch) Permit sub-type (auto-ignored if it doesn't match `facilityType`) |
| `sedationModality` | select | – | (mode=facilitySearch) Sedation modality (only applies when `facilityType=SAFP`) |
| `enrichWithFullProfile` | boolean | `true` | Fetch each result's full detail page. Disable for a faster, list-only scan of large result sets |
| `maxItems` | integer | `20` | Hard cap on emitted records (1–500) |

#### Example: orthodontists in Ottawa who are currently active members

```json
{
  "mode": "search",
  "city": "Ottawa",
  "specialty": "OR",
  "resultFilter": "onlyMembers",
  "maxItems": 50
}
```

#### Example: dentists with public discipline findings

```json
{
  "mode": "search",
  "concernType": "FINDMISCOND",
  "maxItems": 50
}
```

#### Example: look up specific dentists by registration number

```json
{
  "mode": "byRegistrationNumbers",
  "registrationNumbers": ["13577", "7848"]
}
```

#### Example: Sedation & Anesthesia facilities in Toronto

```json
{
  "mode": "facilitySearch",
  "facilityType": "SAFP",
  "city": "Toronto",
  "maxItems": 50
}
```

### Use cases

- **Patient due-diligence** — verify a dentist's registration status, specialty, and public discipline history before booking
- **Referral network building** — find specialists (orthodontists, oral surgeons, etc.) by city or district
- **Facility safety checks** — confirm a practice's Sedation & Anesthesia or CT Scanner permit is current before a sedation procedure
- **Compliance & research** — analyze public discipline/complaint trends across the profession
- **Practice/market research** — map dentist and facility density by city or electoral district

### FAQ

**Where does this data come from?** Directly from the [RCDSO public register](https://www.rcdso.org/find-a-dentist) — the official regulator of dentistry in Ontario, Canada. This is the government-mandated source of truth for dentist licensing, not a third-party aggregator.

**Does this cover all of Canada?** RCDSO regulates Ontario only. Other provinces have their own dental regulatory colleges.

**What does "Not Entitled to Practice" mean?** The dentist previously held an RCDSO registration but currently cannot practise dentistry in Ontario (e.g. resigned, retired, suspended, or revoked). RCDSO does not publish a practice address for these registrants.

**What's the difference between `hasConcerns` and `concernType`?** `hasConcerns` (output) tells you whether a specific dentist has any public concern/complaint/discipline record. `concernType` (input) lets you search for dentists within one of RCDSO's 11 published concern categories.

**Why are some fields missing from a record?** RCDSO only publishes what applies to that dentist or facility — e.g. a dentist with no specialty certificate has no `specialties` field, and one with no additional practice locations has no `additionalPracticeLocations` field. Fields are never filled with placeholder values.

**Why would `permitType` or `sedationModality` be ignored?** `permitType`'s sub-types are specific to the chosen `facilityType` (Sedation & Anesthesia sub-types only apply when `facilityType=SAFP`; CT Scanner sub-types only apply when `facilityType=DCTS`), and `sedationModality` only applies to Sedation & Anesthesia searches. A mismatched combination is automatically ignored (with a note in the run log) rather than silently returning wrong results.

**How current is the data?** RCDSO's register is a live, continuously updated database — every run fetches the current register directly, with no caching.

# Actor input Schema

## `mode` (type: `string`):

What to search.

## `dentistName` (type: `string`):

Dentist's name, as "Last name, First name" (partial matches work too, e.g. just a last name). Leave blank to skip.

## `city` (type: `string`):

Practice/facility city in Ontario. Partial matches work (e.g. "Tor" matches "Toronto"). Leave blank to skip.

## `specialty` (type: `string`):

Restrict to dentists who hold a specialty certificate in this area.

## `district` (type: `string`):

Restrict to dentists registered in one of RCDSO's 12 designated electoral districts.

## `concernType` (type: `string`):

Restrict to dentists with a specific category of public concern, complaint outcome, or discipline finding on their record.

## `practiceName` (type: `string`):

Name of the dental practice/clinic. Partial matches work. Leave blank to skip.

## `registrationNumber` (type: `string`):

Narrow the search mode to one exact RCDSO registration number. Leave blank to skip. For looking up several known numbers at once, use mode=byRegistrationNumbers instead.

## `address` (type: `string`):

Street address of the practice/facility. Partial matches work. Leave blank to skip.

## `phone` (type: `string`):

Practice/facility phone number (any format, e.g. "416" or "416-555-1234"). Leave blank to skip.

## `registrationNumbers` (type: `array`):

One or more exact RCDSO registration numbers to look up. Required for mode=byRegistrationNumbers.

## `facilityType` (type: `string`):

Restrict to facilities holding this type of permit.

## `facilityName` (type: `string`):

Name of the dental facility/clinic. Partial matches work. Leave blank to skip.

## `permitType` (type: `string`):

Restrict to a specific permit sub-type. Sedation & Anesthesia sub-types (Type A/B) only apply when Facility permit type = Sedation & Anesthesia; CT Scanner sub-types (Craniofacial/Dentoalveolar) only apply when Facility permit type = CT Scanner. Mismatched combinations are ignored automatically.

## `sedationModality` (type: `string`):

Restrict Sedation & Anesthesia facility results to a specific modality administered on-site. Only applies when Facility permit type = Sedation & Anesthesia.

## `resultFilter` (type: `string`):

Client-side quick filter mirroring RCDSO's own "Only Members" / "Has Conditions" / "Not Entitled" result options.

## `sortBy` (type: `string`):

Order dentist results by name.

## `enrichWithFullProfile` (type: `boolean`):

When enabled (default), fetches each dentist's/facility's full detail page for complete data (academic history, certificates, disciplinary case files, permits). When disabled, only the fast list-level fields (name, status/type, address, phone) are returned — useful for quickly browsing very large result sets.

## `maxItems` (type: `integer`):

Maximum number of records to return in one run.

## Actor input object example

```json
{
  "mode": "search",
  "dentistName": "Smith",
  "city": "",
  "specialty": "",
  "district": "",
  "concernType": "",
  "practiceName": "",
  "registrationNumber": "",
  "address": "",
  "phone": "",
  "registrationNumbers": [
    "13577"
  ],
  "facilityType": "SAFP",
  "facilityName": "",
  "permitType": "",
  "sedationModality": "",
  "resultFilter": "",
  "sortBy": "nameAsc",
  "enrichWithFullProfile": true,
  "maxItems": 20
}
```

# Actor output Schema

## `dentists` (type: `string`):

Dataset containing all scraped RCDSO dentist and dental facility records.

# 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 = {
    "mode": "search",
    "dentistName": "Smith",
    "city": "",
    "specialty": "",
    "district": "",
    "concernType": "",
    "practiceName": "",
    "registrationNumber": "",
    "address": "",
    "phone": "",
    "registrationNumbers": [
        "13577"
    ],
    "facilityType": "SAFP",
    "facilityName": "",
    "permitType": "",
    "sedationModality": "",
    "resultFilter": "",
    "sortBy": "nameAsc",
    "enrichWithFullProfile": true,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/ontario-dentist-register-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 = {
    "mode": "search",
    "dentistName": "Smith",
    "city": "",
    "specialty": "",
    "district": "",
    "concernType": "",
    "practiceName": "",
    "registrationNumber": "",
    "address": "",
    "phone": "",
    "registrationNumbers": ["13577"],
    "facilityType": "SAFP",
    "facilityName": "",
    "permitType": "",
    "sedationModality": "",
    "resultFilter": "",
    "sortBy": "nameAsc",
    "enrichWithFullProfile": True,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/ontario-dentist-register-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 '{
  "mode": "search",
  "dentistName": "Smith",
  "city": "",
  "specialty": "",
  "district": "",
  "concernType": "",
  "practiceName": "",
  "registrationNumber": "",
  "address": "",
  "phone": "",
  "registrationNumbers": [
    "13577"
  ],
  "facilityType": "SAFP",
  "facilityName": "",
  "permitType": "",
  "sedationModality": "",
  "resultFilter": "",
  "sortBy": "nameAsc",
  "enrichWithFullProfile": true,
  "maxItems": 20
}' |
apify call crawlerbros/ontario-dentist-register-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/ontario-dentist-register-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/gJT12NR0GVQW2Dgsu/builds/eAsNiU2JKmxvNo4VY/openapi.json
