# Canadian Registry Lookup — licence checks at the source (`alamr/registry-lookup-canada`) Actor

Checks a person against a Canadian regulator's public register at the moment you ask, and returns the answer with the timestamp of the actual query and the source URL. No stored copy is served as an answer.

- **URL**: https://apify.com/alamr/registry-lookup-canada.md
- **Developed by:** [Danilo Dzyuban](https://apify.com/alamr) (community)
- **Categories:** Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 completed registry checks

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?

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

## Canadian Registry Lookup

**Asks the regulator. Now. And tells you when it asked.**

You give a name or a licence number. It queries the regulator's own public
register at that moment and returns what the register says — with the UTC
timestamp of the actual query and the URL of the source.

No stored copy is ever served as an answer. A verification that quietly returns
last week's snapshot is worse than no verification: licences are suspended on
Tuesdays too, and the person relying on your check is the one who pays for it.

```json
{
  "registry": "reco-on",
  "verdict": "on_register",
  "matches": [{
    "name": "Allison LANE Guthrie",
    "category": "Broker",
    "licence_number": "4715694",
    "status": "REGISTERED",
    "expiry": "2026/11/06",
    "firm": "SIMCOE HILLS REAL ESTATE INC.",
    "address": "301 Laclie St. Unit: 2 Orillia, ON L3V 4N9 CANADA",
    "phone": "705-325-6262"
  }],
  "match_count": 1,
  "as_of": "2026-08-11T04:36:45+00:00",
  "source": "https://registrantsearch.reco.on.ca/",
  "source_live": true,
  "regulator": "Real Estate Council of Ontario (RECO)",
  "billable": true
}
```

### Registers covered

| Slug | Regulator | Who | Requires |
|---|---|---|---|
| `reco-on` | Real Estate Council of Ontario | salespersons and brokers | `city` |
| `bccohp-bc` | BC College of Oral Health Professionals | six oral health professions | `profession` |
| `cpspei-pe` | College of Physicians and Surgeons of Prince Edward Island | physicians | `name` |
| `cotbc-bc` | College of Occupational Therapists of British Columbia | occupational therapists | `name` |
| `acot-ab` | Alberta College of Occupational Therapists | occupational therapists | `name` |
| `bccsw-bc` | BC College of Social Workers | social workers | `name` |
| `lss-sk` | Law Society of Saskatchewan | lawyers | `name` |
| `mvma-mb` | Manitoba Veterinary Medical Association | veterinarians | `name` |
| `nbds-nb` | New Brunswick Dental Society | dentists | `name` |
| `acsw-ab` | Alberta College of Social Workers | social workers | `name` |
| `cap-ab` | College of Alberta Psychologists | psychologists | `name` |
| `cdhm-mb` | College of Dental Hygienists of Manitoba | dental hygienists | `name` |
| `cdhs-sk` | College of Dental Hygienists of Saskatchewan | dental hygienists | `name` |
| `clpnm-mb` | College of Licensed Practical Nurses of Manitoba | licensed practical nurses | `name` |
| `clpnnl-nl` | College of Licensed Practical Nurses of Newfoundland and Labrador | licensed practical nurses | `name` |
| `clpns-ns` | College of Licensed Practical Nurses of Nova Scotia | licensed practical nurses | `name` |
| `cotm-mb` | College of Occupational Therapists of Manitoba | occupational therapists | `name` |
| `cphm-mb` | College of Pharmacists of Manitoba | pharmacists | `name` |
| `cpsnb-nb` | College of Physicians and Surgeons of New Brunswick | physicians | `name` |
| `cpsnl-nl` | College of Physicians and Surgeons of Newfoundland and Labrador | physicians | `name` |
| `cpta-ab` | College of Physiotherapists of Alberta | physiotherapists | `name` |
| `cptbc-bc` | College of Physical Therapists of British Columbia | physical therapists | `name` |
| `cptnb-nb` | College of Physiotherapists of New Brunswick | physiotherapists | `name` |
| `crnm-mb` | College of Registered Nurses of Manitoba | registered nurses | `name` |
| `crnnl-nl` | College of Registered Nurses of Newfoundland and Labrador | registered nurses | `name` |
| `crns-sk` | College of Registered Nurses of Saskatchewan | registered nurses | `name` |
| `nlchp-nl` | Newfoundland and Labrador College of Health Professionals | allied health professionals | `name` |

Every one of these was queried live while building this, and every one is
re-checked by `validate.py`, which asks all of them in one pass and prints which
answered. A register that is in the table but silent is worse than one that is
missing — the buyer assumes it works. So the table is what survives that check,
not what was once found.

RECO returned 83 registrants for Orillia in a single request; BCCOHP returned 267
denturists for British Columbia; the nursing colleges answer by name.

**Last full validation: 13 August 2026 — 27 of 27 registers answered, in 90 seconds.**
Re-run it yourself with `python3 validate.py`.

Twenty-five of the twenty-seven run on **Alinity**, a platform used by 45+ Canadian health
regulators — so the same adapter reaches far more registers than are listed here.
Adding one is a row in a table, not new code. They are added on request rather
than in bulk, because every register added is a permanent maintenance obligation.

### Verdicts

| `verdict` | Meaning | Charged |
|---|---|---|
| `on_register` | Listed, and the regulator publishes standing | yes |
| `on_register_status_not_published` | Listed, but this regulator shows standing only on the detail page — **not** the same as "in good standing" | yes |
| `not_found` | The register answered and has no such record | yes |
| `ambiguous` | Several records match; narrow by licence number or city | yes |
| `source_unavailable` | The regulator did not answer, or a required field was missing | **no** |

**You are charged per completed check, not per match.** A confirmed "this person
is not on the register" is usually the exact answer you came for. You are never
charged when the regulator fails to answer — that is our problem, not your bill.

### Why the required fields are not optional

RECO's register refuses an empty city — that is their rule, not ours, and a tool
that quietly substituted "Toronto" would be inventing an answer. BCCOHP requires
a profession because its six professions are separate registers behind one form.

If you do not know the city, you can supply a routing index (see below) and the
lookup will find it locally, then still ask the regulator for the answer itself.

### What this tool will not do

- **It will not read standing where the regulator does not publish it.** BCCOHP
  shows discipline history and standing on a per-person detail page; the search
  results do not carry it. The verdict says so explicitly instead of implying
  good standing from mere presence in a list.
- **It will not defeat a captcha.** The Law Society of BC deliberately puts a
  captcha in front of member email addresses. That is a considered decision by a
  regulator about its members' data, and it is respected here — which is also
  why LSBC is not in the table above.
- **It will not invent a field.** Where the source leaves something blank, the
  answer is `null`. A fabricated licence number is worse than a missing one,
  because a missing one gets checked.

### Usage

#### As an Apify Actor

```json
{
  "registry": "reco-on",
  "people": [
    { "name": "Allison Lane Guthrie", "city": "Orillia" },
    { "licence_number": "4715694", "city": "Orillia" }
  ]
}
```

Set `listRegistries: true` to get the catalogue instead of running checks.

#### From the command line

```bash
python3 cli.py --registries
python3 cli.py --registry reco-on --name "Allison Lane Guthrie" --city Orillia
python3 cli.py --registry bccohp-bc --name "Ahmed Abu-Omar" --profession denturist
python3 cli.py --in people.jsonl --out checked.jsonl --registry reco-on
```

#### Optional: routing index

If you already hold a bulk export of a register, point the tool at it:

```bash
export REGISTRY_INDEX_DIR=/path/to/exports    # expects <slug>.jsonl
```

The index answers exactly one question — *which city should I ask about* — when
the caller does not know it. Nothing from the index reaches the answer: not the
status, not the licence number, not the address. Those come from the live query
or not at all. Loading a 97,000-record export takes about three seconds.

### Manners

One request at a time, answers cached for the run, and a gap between calls.
Checking a hundred people from one city is **one** request to the regulator, not a
hundred. These registers are published under professional regulation statutes for
exactly this kind of check; hammering them would be both rude and self-defeating.

Two details learned the hard way, both on 13 August 2026:

**The rate limit is per platform, not per host.** Twenty-five of these registers
run on the same Alinity installation base. Pacing requests per subdomain — as this
tool first did — is not pacing at all: a sweep across twelve of them knocked over
half, and registers that had answered a minute earlier started failing. The gap is
now counted across the whole platform.

**A refusal is not a dead register.** A full pass over the catalogue ran fine for
21 requests and then failed on every remaining one — a cumulative limit, not six
broken regulators. All six answered normally on retry. Requests now back off and
retry rather than reporting the register as unavailable, because a client checking
fifty people should not be told that half the country's colleges are offline.

### Fragility, stated up front

These are scraped HTML forms, not documented APIs. Regulators redesign pages
without notice, and when that happens the adapter raises an error naming the
register rather than returning an empty result. Silence that looks like an answer
is the one failure mode this tool refuses to have.

Observed during development: one regulator on the Alinity platform changed its
category values between 2 and 10 August 2026 and began returning zero records for
a previously working query. It is not in the table above for that reason.

### License

MIT.

# Actor input Schema

## `registry` (type: `string`):

Which regulator to ask. Run with `listRegistries` to see what each one requires.

## `people` (type: `array`):

Each item: { "name": "...", "licence\_number": "...", "city": "..." }. A licence number, when given, outranks the name.

## `name` (type: `string`):

Shortcut for one check. Ignored when People is set.

## `licenceNumber` (type: `string`):

Exact registration number. Beats the name if both are given.

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

Required by RECO — its register refuses an empty city. Supply it, or the check is refused rather than guessed.

## `profession` (type: `string`):

Required by BCCOHP: dentist, dental-hygienist, dental-assistant, dental-technician, dental-therapist, denturist.

## `listRegistries` (type: `boolean`):

Returns the catalogue: regulator, jurisdiction, profession and which fields that register requires.

## Actor input object example

```json
{
  "registry": "reco-on",
  "people": [
    {
      "name": "Allison Lane Guthrie",
      "city": "Orillia"
    }
  ],
  "profession": "dentist",
  "listRegistries": false
}
```

# Actor output Schema

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

All checks from this run, with verdict, matched records, source and the UTC time the regulator was asked.

## `dataset` (type: `string`):

The same results as raw JSON, for loading into your own systems.

# 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 = {
    "people": [
        {
            "name": "Allison Lane Guthrie",
            "city": "Orillia"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("alamr/registry-lookup-canada").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 = { "people": [{
            "name": "Allison Lane Guthrie",
            "city": "Orillia",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("alamr/registry-lookup-canada").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 '{
  "people": [
    {
      "name": "Allison Lane Guthrie",
      "city": "Orillia"
    }
  ]
}' |
apify call alamr/registry-lookup-canada --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,alamr/registry-lookup-canada"
        }
    }
}

```

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/VQdaiPiH4O3Has90O/builds/xhWRP5u4g32wjV05L/openapi.json
