# US Professional License Lookup & Verification API (`owlabee/us-license-verifier`) Actor

Look up and verify professional licenses by name or license number from official public records: Texas TDLR, Florida DBPR, Colorado DORA, Washington DOH and the federal NPI Registry. Normalized status, expiration and discipline flags. Pay per license record.

- **URL**: https://apify.com/owlabee/us-license-verifier.md
- **Developed by:** [Owlabee](https://apify.com/owlabee) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 license records

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

## US Professional License Lookup & Verification API

Look up and verify professional licenses by **name or license number** from official public records, and get one normalized JSON record per license: state, board, license number and type, profession, normalized status (`ACTIVE`, `EXPIRED`, `SUSPENDED`, `REVOKED`, `PROBATION`, `PENDING`, `INACTIVE`, `UNKNOWN`) plus the board's raw status, issue and expiration dates, city, county, zip, a disciplinary-action flag and a link back to the source. In **verify** mode every record also carries `verified: true|false` and plain-English `verificationNotes`.

### What it does

The Actor queries only official, public license registries, over plain HTTP, with no login, browser or proxy:

| Source id | Board | Coverage | How it is queried |
|---|---|---|---|
| `TX_TDLR` | Texas Department of Licensing and Regulation | ~1.0 M licenses: electricians, A/C & HVAC technicians and contractors, cosmetologists, barbers, salons, tow operators, behavior analysts, property tax professionals, auctioneers, water well drillers, elevator and boiler inspectors, … | Socrata open-data API on data.texas.gov (dataset `7358-krk7`) |
| `FL_DBPR` | Florida Department of Business & Professional Regulation | Real estate, construction contractors, community association managers, home inspectors, cosmetology and barbers, CPAs, architects, engineers, veterinarians, hotels & restaurants, … | The public "Verify a Licensee" portal on myfloridalicense.com (one HTML request per query, 1 request/second) |
| `CO_DORA` | Colorado Department of Regulatory Agencies, Division of Professions and Occupations | ~1.6 M licenses across every DORA profession: nurses, physicians, dentists, pharmacists, CPAs, cosmetologists, electricians, plumbers, real estate, … including disciplinary case numbers | Socrata open-data API on data.colorado.gov (dataset `7s5z-vewr`, updated daily) |
| `WA_DOH` | Washington State Department of Health | ~2.5 M health-care credentials: RNs, LPNs, ARNPs, nursing assistants, physicians, EMTs, counselors, pharmacists, … with an "action taken" flag | Socrata open-data API on data.wa.gov (dataset `qxh8-f4bd`, updated daily) |
| `NPI` | CMS NPPES NPI Registry (federal) | Every US health-care provider and organization with an NPI; includes the self-reported state license number and taxonomy | Official JSON API at npiregistry.cms.hhs.gov |

It is built for compliance teams, staffing agencies, insurers, marketplaces and procurement teams that need to check contractor, cosmetology, real-estate or healthcare licenses in bulk, and for AI agents that need a structured answer to "is this license active?". Results arrive in the dataset as they are found and can be downloaded as JSON, CSV or Excel, read through the API, pushed to a webhook, or consumed through the Apify MCP server.

### How to use

1. Enter **Queries**, one per line, e.g. `TX:3365:Master Electrician`, `FL:SEC17708`, `name:Jane Smith:CO`, `npi:1003000126`. For exact fields (city, board) use **Lookups (structured)** instead: `[{"state": "CO", "name": "Smith, John", "licenseType": "RN", "city": "Denver"}]`.
2. Pick the **Mode**: `lookup` lists every matching license; `verify` adds `verified` and `verificationNotes`. Optionally narrow **Sources** and set **Max records per query**.
3. Click **Start**. For scheduled monitoring, turn on **Monitor mode** so later runs emit only licenses whose status or expiration changed.

#### Query routing

- A query **with a state** (`TX:…`, `name:…:FL`) is answered by that state's board(s) only.
- A query **without a state** (`name:Jane Smith`) is answered by every enabled source, including the NPI Registry.
- A **board** (`npi:…`, `tdlr:…`, `dbpr:…`, `dora:…`, `doh:…`) pins the query to one source. A bare 10-digit number is treated as an NPI.
- License numbers repeat across license types in Texas and Colorado; add the type (`TX:3365:Master Electrician`, `CO:RN.123456`) to get a single record.

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `queries` | string\[] | one of `queries` / `lookups` | Lenient one-line queries: `STATE:NUMBER[:TYPE]`, `name:NAME[:STATE]`, `npi:NUMBER`, `npi:NAME[:STATE]`, `BOARD:…` |
| `lookups` | object\[] | one of `queries` / `lookups` | Structured queries: `state`, `licenseNumber`, `name`, `licenseType`, `city`, `board` |
| `mode` | `lookup` | `verify` | no | `verify` adds `verified` and `verificationNotes` (default `lookup`) |
| `sources` | string\[] | no | Subset of `TX_TDLR`, `FL_DBPR`, `CO_DORA`, `WA_DOH`, `NPI` (default all) |
| `maxResultsPerQuery` | integer | no | Cap per query across sources, 1–200 (default 25) |
| `maxResults` | integer | no | Cap on dataset items per run (default 500) |
| `monitor` | boolean | no | Emit only changes since the last run (default false) |
| `stateStoreName` | string | no | KV store for monitor snapshots (default `us-license-verifier-state`) |
| `socrataAppToken` | string (secret) | no | Optional Socrata app token to raise rate limits on the TX/CO/WA datasets |
| `maxConcurrency` | integer | no | Parallel queries, 1–5 (default 3) |
| `webhookUrl` | string | no | POST target for the run summary and emitted items |

### Output example

A verified Texas master electrician (`verify` mode, query `TX:3365:Master Electrician`):

```json
{
  "state": "TX",
  "board": "TDLR",
  "source": "TX_TDLR",
  "licenseNumber": "3365",
  "licenseType": "Master Electrician",
  "licenseSubtype": "ME",
  "profession": "Electrical",
  "status": "ACTIVE",
  "rawStatus": null,
  "statusDerived": true,
  "holderName": "Womack, Thomas F",
  "businessName": null,
  "issueDate": null,
  "expirationDate": "2027-03-19",
  "city": null,
  "county": "Walker",
  "zip": null,
  "disciplinaryActionFlag": null,
  "continuingEducationRequired": false,
  "sourceUrl": "https://www.tdlr.texas.gov/LicenseSearch/",
  "sourceRecordId": "MASTER ELECTRICIAN#3365",
  "fetchedAt": "2026-10-16T13:18:49.662Z",
  "query": "TX:3365:Master Electrician",
  "queryKey": "tx:3365:master-electrician-9e1c2a7b",
  "found": true,
  "verified": true,
  "verificationNotes": [
    "Active; expires 2027-03-19.",
    "Status derived from the expiration date because the source publishes no status field.",
    "Confirm with the issuing board before relying on this result."
  ]
}
```

A Colorado nurse with a board action (`CO_DORA`) adds `licenseTypeCode`, `licenseCategory`, `lastRenewedDate`, `disciplinaryActionFlag: true` and `disciplinaryAction: { caseNumber, action, effectiveDate, completedDate }`. Washington records add `disciplinaryActionPending`. Florida records add `nameType` and `addressState`. NPI records add `enumerationType`, `taxonomyCode`, `stateLicenseNumber`, `stateLicenseState` and `allTaxonomies`.

Other item shapes:

- **Not found:** `{ "query": "FL:ZZZZ99999", "found": false, "state": "FL", "sourcesQueried": ["FL_DBPR"], "verified": false, "verificationNotes": ["No matching record …"] }`
- **Error:** `{ "query": "CA:123456", "found": false, "error": "INVALID_QUERY", "errorMessage": "State CA is not supported yet. Supported: TX, FL, CO, WA …" }`. Error codes: `SOURCE_UNAVAILABLE` (timeout, 5xx), `SOURCE_BLOCKED` (403/429/block page), `INVALID_QUERY` (unsupported state or board, missing number and name, rejected filter).
- **Monitor change:** a normal license record plus `changeType` (`new` / `changed` / `removed`), `changedFields`, `previousStatus`, `previousExpirationDate`.

### Pricing

Pay per event. You only pay for what the Actor produces; set a **maximum cost per run** in the run options to cap spend.

| Event | Price | Notes |
|---|---|---|
| License record (`license-verified`) | $0.004 | Per dataset item in lookup or verify mode: each license found, each not-found result and each error item |
| Monitor re-check (`monitor-check`) | $0.002 | Per query re-checked in monitor mode (not on the baseline run); changed records are charged as license records on top |
| Actor start | $0.00005 | Apify platform event |

**Example:** 1,000 verifications ≈ **$4.00**. A daily monitor of 100 licenses costs $0.20 per run when nothing changed, plus $0.004 per changed record. The first monitor run is a baseline that emits and charges every current record.

### Limits and fair use

- Each source is rate-limited to about one request per second; Socrata datasets accept an optional free app token for higher limits. Very large inputs take proportionally longer (expect roughly 1–3 s per query per source).
- Name searches on common names can match hundreds of licenses; `maxResultsPerQuery` (default 25, max 200) caps them. Narrow with a state, license type or city.
- Florida returns the first 50 matches of the portal's result page; Texas, Colorado and Washington return up to `maxResultsPerQuery` matches.
- Texas publishes no status column, so the Texas status is derived from the expiration date (`statusDerived: true`). Texas and Colorado open-data extracts are refreshed by the boards (Colorado and Washington daily, Texas less often); results may lag the board's live database.
- Only public records are accessed; no login, no proxies, no personal data beyond what the board publishes.

### Related Actors

Other pay-per-event Actors from the same account, all limited-permission and agent-friendly:

- [US Building Permits Scraper — City & County Permit Records](https://apify.com/owlabee/building-permits-scraper) — building permits with contractor names and license numbers to verify.
- [Domain WHOIS, DNS, Email Security & TLS Checker](https://apify.com/owlabee/domain-intelligence) — enrich the contractor's or clinic's domain with WHOIS, DNS, email security and tech stack.
- [US Government Grants & Contract Opportunities Feed](https://apify.com/owlabee/gov-opportunity-feed) — find who is bidding for or winning public work.

### FAQ

**Does this need proxies or an API key?** No. All five sources are public and reachable from Apify's standard datacenter infrastructure. A free Socrata app token is optional and only raises rate limits.

**How is cost controlled?** Set `maxResultsPerQuery`, `maxResults` and the run's maximum total charge. The Actor charges each item the moment it is saved and stops cleanly when any limit is reached.

**Which states are covered?** Texas (TDLR trades and occupations), Florida (DBPR professions), Colorado (all DORA professions) and Washington (health-care credentials), plus every US health-care provider through the federal NPI Registry. Unsupported states return an `INVALID_QUERY` item naming the supported ones. California's DCA and CSLB lookups sit behind Cloudflare Turnstile and are not included.

**What does `verified` mean exactly?** `found` and `status === "ACTIVE"` and the expiration date is today or later. Statuses such as "Active - With Conditions" normalize to `PROBATION` and therefore do not verify; the raw status is always kept in `rawStatus` and explained in `verificationNotes`.

**Can an AI agent call it?** Yes. It is pay-per-event, uses limited permissions and is discoverable through the Apify MCP server. Example prompts: "Verify Texas master electrician license 3365 and tell me when it expires", "Find every active Colorado RN named Smith, John in Denver", "Check whether NPI 1003000126 is active and which state license it lists".

**How does monitor mode work?** Each query's current licenses are fingerprinted (status + expiration date) in the named key-value store. Later runs emit only `new`, `changed` or `removed` records and charge one `monitor-check` per query. If a source errors during a run, that query's snapshot is kept so licenses are not reported as removed by mistake.

### Legal

This Actor queries only official public license registries and reproduces what they publish; records may lag the issuing board's live database and should be confirmed with the board before any decision is based on them (every record links back to the official lookup in `sourceUrl`).

**FCRA disclaimer.** This Actor is not a consumer reporting agency and its output is not a consumer report. The data must not be used, in whole or in part, to determine eligibility for employment, credit, insurance, housing or any other purpose covered by the Fair Credit Reporting Act or similar laws. Verify with the issuing board. Use the data in compliance with each source's terms of use and applicable law.

# Actor input Schema

## `queries` (type: `array`):

One license query per line, parsed leniently: "TX:3365:Master Electrician" (state:number\[:type]), "FL:SEC17708", "name:Jane Smith:FL" (name limited to a state), "name:Jane Smith" (name across every enabled source), "npi:1003000126" (NPI number), "npi:Jane Smith:TX" (NPI by name). Use "Lookups (structured)" for city or exact fields. The prefilled values are the cheap, always-available examples used by Apify's daily automated test.

## `lookups` (type: `array`):

Optional JSON array of query objects: \[{"state": "TX", "licenseNumber": "3365", "licenseType": "Master Electrician"}, {"state": "CO", "name": "Smith, John", "licenseType": "RN"}, {"board": "NPI", "name": "Mayo Clinic", "state": "MN"}]. Fields: state, licenseNumber, name (person "Last, First" / "First Last" or business), licenseType, city, board (TDLR, DBPR, DORA, DOH, NPI). Combined with "Queries".

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

lookup = return every matching license record. verify = same records plus verified (true only when the license is found, status is ACTIVE and it is not expired) and verificationNotes.

## `sources` (type: `array`):

Which official public sources to query. A query with a state only hits that state's board(s); a query with no state hits every enabled source; board "NPI" (or a bare 10-digit number) hits the federal NPI Registry.

## `maxResultsPerQuery` (type: `integer`):

Cap on license records returned for one query across all sources (1–200). Name searches on common names can match hundreds of licenses; narrow them with a state, license type or city.

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

Upper bound on dataset items per run (license records, not-found results and error items). Use together with the run's maximum cost to control spend.

## `monitor` (type: `boolean`):

For schedules: remember every license seen per query in the state store and emit only records whose status or expiration date changed, plus licenses that appeared or disappeared (changeType new / changed / removed). The first run is a baseline and emits (and charges for) everything; later runs charge one monitor re-check per query.

## `stateStoreName` (type: `string`):

Named key-value store that holds the monitor snapshots. Use a different name per schedule if two schedules share queries.

## `socrataAppToken` (type: `string`):

Free app token from any Socrata open-data portal (e.g. data.texas.gov → Developer Settings). Raises the per-IP rate limit for the Texas, Colorado and Washington datasets; not required for small runs. Stored encrypted and never logged.

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

How many queries run in parallel (1–5). Each source is still rate-limited to about one request per second.

## `webhookUrl` (type: `string`):

If set, the Actor POSTs a JSON run summary plus the emitted items (max 500) to this URL when it finishes. Failures are logged, never fatal.

## Actor input object example

```json
{
  "queries": [
    "npi:1003000126",
    "name:John Smith:TX"
  ],
  "lookups": [],
  "mode": "verify",
  "sources": [
    "TX_TDLR",
    "FL_DBPR",
    "CO_DORA",
    "WA_DOH",
    "NPI"
  ],
  "maxResultsPerQuery": 5,
  "maxResults": 20,
  "monitor": false,
  "stateStoreName": "us-license-verifier-state",
  "maxConcurrency": 3
}
```

# Actor output Schema

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

One item per license record in the default dataset (state, board, licenseNumber, licenseType, holderName, businessName, status, expirationDate, verified, disciplinaryActionFlag, sourceUrl; error items carry error and errorMessage). Fields are described in dataset_schema.json.

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

SUMMARY record in the run's default key-value store: lookups performed, records produced, charges and webhook delivery status, written when the run finishes.

# 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 = {
    "queries": [
        "npi:1003000126",
        "name:John Smith:TX"
    ],
    "lookups": [],
    "mode": "verify",
    "sources": [
        "TX_TDLR",
        "FL_DBPR",
        "CO_DORA",
        "WA_DOH",
        "NPI"
    ],
    "maxResultsPerQuery": 5,
    "maxResults": 20,
    "monitor": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("owlabee/us-license-verifier").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 = {
    "queries": [
        "npi:1003000126",
        "name:John Smith:TX",
    ],
    "lookups": [],
    "mode": "verify",
    "sources": [
        "TX_TDLR",
        "FL_DBPR",
        "CO_DORA",
        "WA_DOH",
        "NPI",
    ],
    "maxResultsPerQuery": 5,
    "maxResults": 20,
    "monitor": False,
}

# Run the Actor and wait for it to finish
run = client.actor("owlabee/us-license-verifier").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 '{
  "queries": [
    "npi:1003000126",
    "name:John Smith:TX"
  ],
  "lookups": [],
  "mode": "verify",
  "sources": [
    "TX_TDLR",
    "FL_DBPR",
    "CO_DORA",
    "WA_DOH",
    "NPI"
  ],
  "maxResultsPerQuery": 5,
  "maxResults": 20,
  "monitor": false
}' |
apify call owlabee/us-license-verifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,owlabee/us-license-verifier"
        }
    }
}
```

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/aNFg4UD8faIQ8GnEH/builds/L492od0hUqSvqbbtH/openapi.json
