# License Verification API — TDLR, DBPR, DORA, WA DOH, NPI (`owlabee/license-verification-api`) Actor

Verify professional licenses by license number (or by name within one state board) from official public records: Texas TDLR, Florida DBPR, Colorado DORA, Washington DOH and the federal NPI Registry. Normalized status and expiration; discipline on request. Pay per record. Not a consumer report.

- **URL**: https://apify.com/owlabee/license-verification-api.md
- **Developed by:** [Owlabee](https://apify.com/owlabee) (community)
- **Categories:** Lead generation, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 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.
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

## License Verification API — TDLR, DBPR, DORA, WA DOH, NPI

![License Verification API — TDLR, DBPR, DORA, WA DOH, NPI: banner with the main output fields](https://api.apify.com/v2/key-value-stores/r8pLvIribNPHYI9gX/records/us-license-verifier-banner.png)

**License verification API** for US professional licenses: look up licenses by **license number** (or by name within one state board) in Texas TDLR, Florida DBPR, Colorado DORA, Washington DOH and the federal NPI Registry, 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, an optional disciplinary-action flag (`includeDiscipline`) and a link back to the source. In **verify** mode every record also carries `verified: true|false` and plain-English `verificationNotes`.

> **Renamed on 2026-11-07:** this Actor's address changed from `owlabee/us-license-verifier` to `owlabee/license-verification-api`. Saved tasks, schedules, webhooks and integrations keep working, because they point to the Actor's ID, which did not change. Update anything that calls it by name: API URLs (`owlabee~license-verification-api`), MCP tool lists (`owlabee/license-verification-api`) and bookmarks.

### 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 credentialing and compliance teams verifying that a business or practitioner holds the license it claims, for marketplaces and procurement teams checking contractor, cosmetology, real-estate or healthcare licenses in bulk, and for AI agents that need a structured answer to "is this license active?". It is a lookup tool over the boards' own public registries, not a background check: see "Legal" below for what it does not do. 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.

Checking contractors you found in permit records? [Building Permits Scraper — Roofing & Construction Leads](https://apify.com/owlabee/building-permits-scraper) supplies the contractor names and license numbers to verify here.

### Why this Actor

| | This Actor | Typical scraper in this category | Do it yourself |
|---|---|---|---|
| Sources covered | Five registries in one run: Texas TDLR, Florida DBPR, Colorado DORA, Washington DOH and the federal NPI Registry, with one query syntax | One agency per Actor, each with its own input shape | One portal or open-data API at a time, each with its own HTML or schema |
| Modes | `lookup`, `verify` (adds `verified` and plain-English notes) and a `monitor` mode that reports status and expiration changes | Lookup, sometimes a status-only flag | Lookup; you keep the history |
| Output fields | One normalized record per license across boards: status in a fixed vocabulary plus the board's raw status, issue and expiration dates, city, county, zip, `sourceUrl`, `verified`, `verificationNotes`; the board's disciplinary flag and details on request (`includeDiscipline`) | The agency's own field names | Whatever each portal returns |
| Pricing unit | Per license record (found, not found or error) and per query re-checked in monitor mode; nothing per source or per minute | Per record, often a higher price for enriched records | Server time and portal upkeep |
| Proxies or API keys | None; a free open-data app token is optional | Often a proxy for the portal | None, until a portal rate-limits you |
| Agent / MCP ready | Yes: pin link, three prompts and a copy-paste `call-actor` block below | Sometimes | No |
| Limits | Four states plus NPI; no California; Florida returns the portal's first 50 matches; Texas status is derived from the expiration date | One state | Whatever you build and maintain |

- **One answer shape for every board.** `status` is normalized across TDLR, DBPR, DORA, DOH and NPI, so a credentialing, compliance or procurement workflow has one rule to apply.
- **Verification you can act on.** `verify` mode says whether the license is active and unexpired, and `verificationNotes` explain why not when it is not.
- **Bulk and monitoring in the same Actor.** Paste a thousand queries, or schedule `monitor` mode and receive only the licenses whose status or expiration changed.
- **Honest about the gaps.** Unsupported states, derived statuses and lagging extracts are flagged on the record rather than hidden.
- **Nothing to configure.** No proxy, login or browser; paste queries and start.

### How to use

1. Enter **Queries**, one per line, license number first: `TX:3365:Master Electrician`, `FL:SEC17708`, `npi:1003000126`; a name lookup names its state or board, `name:Jane Smith:CO`. 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**, set **Max records per query**, and turn on **Include disciplinary data** if you need the board's disciplinary fields (off by default).
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 **license number without a state** (`license:SL3567250`) is answered by every enabled source, including the NPI Registry: a license number identifies a credential, not a person.
- A **name without a state or board** (`name:Jane Smith`) is accepted only when exactly one source is enabled in `sources`; otherwise it returns an `INVALID_QUERY` item asking for a state or a board. The Actor never searches a person's name across several state registries: name lookups are a lookup aid within one board, not a people search (see "Legal").
- 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.

#### Quick start input

The same input the Store runs in its daily automated test, so it is cheap and always available; paste it into the JSON tab of the input form or send it as the body of an API or MCP call:

```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
}
```

#### Ready-to-run inputs

Complete inputs for four common jobs; paste one into the JSON tab and change the queries.

**Texas TDLR lookup** (license number with type, and a name limited to Texas):

```json
{
  "queries": ["TX:3365:Master Electrician", "name:Jane Smith:TX"],
  "mode": "lookup",
  "sources": ["TX_TDLR"],
  "maxResultsPerQuery": 10
}
```

**Florida DBPR verify** (a license number and a name, with `verified` and `verificationNotes` on every record):

```json
{
  "queries": ["FL:SEC17708", "name:Jane Smith:FL"],
  "mode": "verify",
  "sources": ["FL_DBPR"],
  "maxResultsPerQuery": 5
}
```

**NPI lookup** (a number, a provider name by state, and a structured organization lookup):

```json
{
  "queries": ["npi:1003000126", "npi:Jane Smith:TX"],
  "lookups": [{ "board": "NPI", "name": "Mayo Clinic", "state": "MN" }],
  "mode": "lookup",
  "sources": ["NPI"],
  "maxResultsPerQuery": 10
}
```

**Monitor run** (schedule it; the first run is the baseline, later runs emit only `new`, `changed` and `removed` records and POST them to the webhook):

```json
{
  "queries": ["npi:1003000126", "FL:SEC17708", "TX:3365:Master Electrician"],
  "mode": "verify",
  "monitor": true,
  "stateStoreName": "license-watch",
  "webhookUrl": "https://hooks.example.com/license-changes"
}
```

### 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) |
| `includeDiscipline` | boolean | no | Write the board's disciplinary fields (`disciplinaryActionFlag`, Colorado's `disciplinaryAction`, Washington's `disciplinaryActionPending`) as published. Default false: the fields are absent from every record |
| `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

![Sample output of License Verification API — TDLR, DBPR, DORA, WA DOH, NPI as a table: the dataset overview columns from the quick-start run](https://api.apify.com/v2/key-value-stores/r8pLvIribNPHYI9gX/records/us-license-verifier-output.png)

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,
  "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 (`CO_DORA`) adds `licenseTypeCode`, `licenseCategory` and `lastRenewedDate`; with `includeDiscipline: true` it also carries `disciplinaryActionFlag` and, when the board lists an action, `disciplinaryAction: { caseNumber, action, effectiveDate, completedDate }`, and Washington records carry `disciplinaryActionFlag` and `disciplinaryActionPending`. Without `includeDiscipline` none of these three fields is written. Florida records add `nameType` and `addressState`. NPI records add `enumerationType`, `taxonomyCode`, `stateLicenseNumber`, `stateLicenseState` and `allTaxonomies`. No record ever carries a gender, ethnicity, birth year, home or mailing street address, personal telephone or e-mail, whatever the board publishes.

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), `TIME_BUDGET` (the board was abandoned for this run after 60 s without a usable response).
- **Monitor change:** a normal license record plus `changeType` (`new` / `changed` / `removed`), `changedFields`, `previousStatus`, `previousExpirationDate`.

If the output saves you time, a review on the Store page helps other buyers find this Actor; the Store invites you to leave one after you have run it a few times.

### Alerts and delivery

- **Schedules.** Create a schedule in the Apify Console (daily or weekly is typical for license renewals) with `monitor: true`; each run appends only the changed records to its dataset.
- **The Actor's own `webhookUrl`.** After every run the Actor POSTs one JSON document to the URL you set: `actor` and `runId`, the `mode`, the number of `queries` and `invalidQueries`, `produced`, `licensesFound`, `notFound`, `errors`, `monitor`, `monitorChecks`, `includeDiscipline`, the `sources` enabled, the acceptable-use `notice`, the `charges` summary, `limitReached`, `finishedAt`, and `items`, up to 500 of the records written in this run (in monitor mode, only the changes). Delivery has a 10 s timeout and two retries; a failing endpoint never fails the run and is recorded as `webhookDelivered: false` in the run's `SUMMARY` key-value record.
- **Apify integrations.** On the Actor's **Integrations** tab you can attach Slack, Make, Zapier, Google Sheets, email or a generic Actor webhook that fires on run events (`ACTOR.RUN.SUCCEEDED`, `ACTOR.RUN.FAILED`, `ACTOR.RUN.TIMED_OUT`) and carries the run and dataset ids, so any downstream tool can pull the items.
- **API and dataset endpoints.** Start a run and wait for its items in one call with `POST https://api.apify.com/v2/acts/owlabee~license-verification-api/run-sync-get-dataset-items?token=<APIFY_TOKEN>` (the input JSON as the body), or start it asynchronously with `POST /v2/acts/owlabee~license-verification-api/runs` and read `GET /v2/datasets/{datasetId}/items?format=json` (also `csv`, `xlsx`) when it finishes. The run's `SUMMARY` record is at `GET /v2/key-value-stores/{storeId}/records/SUMMARY` and includes `webhookDelivered` when a webhook was set.

### 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 | $0.002 | Per dataset item in lookup or verify mode: each license found, each not-found result and each error item |
| Monitor re-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 ≈ **$2.00**. A daily monitor of 100 licenses costs $0.20 per run when nothing changed, plus $0.002 per changed record. The first monitor run is a baseline that emits and charges every current record.

Store pricing discounts: users on the Scale plan (Silver) pay 10% less per event, Business (Gold) 20% less.

### 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).
- Each board has a time budget for the run: after 60 s in a row without a usable response (timeouts, 5xx, 403/429), that board is abandoned for the rest of the run. Its remaining queries get a charged error item with `error: "TIME_BUDGET"` immediately instead of waiting for another timeout; the other boards keep running. A board that answers but rejects a query (400/404/422) is not counted as down. In monitor mode a query with a source error keeps its previous snapshot. Default run memory is 512 MB.
- Name searches on common names can match hundreds of licenses; `maxResultsPerQuery` (default 25, max 200) caps them. Narrow with a license type or city. A name query is answered by one board: give it a state or a board, or enable a single source.
- 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. Of what a board publishes, the Actor writes the license facts and the holder's name, business, city, county and zip; it never writes gender, ethnicity, birth year, street addresses, personal phone numbers or e-mail, and disciplinary fields only on request (`includeDiscipline`).

### Related Actors

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

- [Building Permits Scraper — Roofing & Construction Leads](https://apify.com/owlabee/building-permits-scraper) — building permits with contractor names and license numbers to verify.
- [SPF, DMARC & DKIM Checker — Bulk Domain WHOIS, DNS & SSL](https://apify.com/owlabee/spf-dmarc-dkim-checker) — enrich the contractor's or clinic's domain with WHOIS, DNS, email security and tech stack.
- [Grants.gov API & SAM.gov Scraper — Federal Grants Feed](https://apify.com/owlabee/grants-gov-api) — find who is bidding for or winning public work.

### Guide and code examples

- Guide: [Professional License Lookup: TDLR, DBPR, DORA, WA DOH, NPI](https://owlabee.com/professional-license-verification/), covering the source, its fields and limits, and curl and MCP examples.
- Runnable examples (curl, Python, Node.js and an MCP config): [owlabee/owlabee on GitHub](https://github.com/owlabee/owlabee/tree/main/examples/license-verification-api).

### Use from AI agents (MCP)

Pin this Actor in the Apify MCP server with one link: `https://mcp.apify.com?tools=owlabee/license-verification-api`, or let the agent find it with the `search-actors` tool. It is pay-per-event with limited permissions, so agents can discover it, inspect the input schema with `fetch-actor-details` and pay per run through agentic payments. Prompts that map directly onto the input:

1. "Verify Texas master electrician license 3365 — is it active, who holds it and when does it expire?"
2. "Look up the Colorado registered-nurse licenses for Smith, John in Denver and tell me which are active and unexpired."
3. "Check NPI 1003000126 and Florida license SEC17708 every morning and send me a webhook only when a status or expiration date changes."

The agent receives the same dataset records shown in the output example above.

Copy this block to call it through the Apify MCP server with the quick-start input; the agent gets the dataset items back in the tool result and can follow up with `get-dataset-items` for larger runs:

```json
{
  "tool": "call-actor",
  "arguments": {
    "actor": "owlabee/license-verification-api",
    "input": {
      "queries": ["npi:1003000126", "name:John Smith:TX"],
      "mode": "verify",
      "maxResultsPerQuery": 5,
      "maxResults": 20
    }
  }
}
```

### 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.

**Can I search a person by name across all states?** No. A name lookup is answered by one board, so it needs a state (`name:Jane Smith:FL`), a board (`npi:Jane Smith`) or a single entry in `sources`; a bare `name:Jane Smith` with several sources enabled returns an `INVALID_QUERY` item. License-number lookups may span sources. This is deliberate: the Actor verifies a credential someone has given you; it is not a people search or a background check.

**Where is the disciplinary flag?** Off by default since 2026-11-07. Set `includeDiscipline: true` and Colorado and Washington records carry the board's own disciplinary fields as published (Texas, Florida and NPI publish none). The Actor adds no severity or risk score.

**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 use this?** Yes; see "Use from AI agents (MCP)" above for the pin link and example prompts. Schedules and webhooks work the same way from the Console or the API.

**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.

**Can I try it for free?** Yes. The Apify free plan comes with monthly platform credit, and the quick-start input above costs a few cents per run, so you can test it on your own inputs before paying anything.

**How do I find this Actor again?** Bookmark it on the Store page (the bookmark icon next to the title); bookmarked Actors are listed in your Console, and the Store page shows new versions as they ship.

### Support

Questions, bugs or a source you'd like added? Open an issue on the Actor's **Issues** tab, or email jacob@owlabee.com.

### Changelog

- **2026-11-07** — Legal guardrails (docs/legal/GUARDRAILS.md): a name query without a state or board is answered only when a single source is enabled and otherwise returns `INVALID_QUERY` (it used to fan out across every enabled registry); disciplinary fields are opt-in with the new `includeDiscipline` input, default off (`disciplinaryActionFlag`, `disciplinaryAction` and `disciplinaryActionPending` are no longer written unless it is set); personal fields a board may publish (gender, ethnicity, birth year, street address, personal phone or e-mail) are stripped at every level; the run `SUMMARY` and webhook carry an acceptable-use `notice`. Prices unchanged.
- **2026-11-07** — Renamed from `owlabee/us-license-verifier` to `owlabee/license-verification-api` so the address matches what people search for. Input, output and pricing are unchanged; calls by Actor ID, saved tasks and schedules are unaffected. Name-based calls to the old address stop resolving: update API URLs and MCP tool lists.
- **2026-10-27** — README: comparison table, ready-to-run inputs, alerts and delivery, MCP call example, changelog.
- **2026-10-25** — Pricing round (in the Console from 2026-10-26): event titles pluralized and Store discount tiers for the Scale and Business plans; no event or price changed.
- **2026-10-23** — Per-board time budget: a board with 60 s of unusable responses is abandoned for the run and its remaining queries get charged `TIME_BUDGET` error items at once; request deadlines include retries; unit-economics test.
- **2026-10-23** — Store listing: title now names TDLR, DBPR, DORA and NPI, SEO fields, quick start input, MCP section, agency-specific task examples.
- **2026-10-18** — Published on the Apify Store with pay-per-event pricing; charged events verified on a platform run.
- **2026-10-16** — First release: Texas TDLR, Florida DBPR, Colorado DORA, Washington DOH and NPI adapters, lenient one-line queries and structured lookups, `lookup`, `verify` and `monitor` modes, webhook delivery.

### 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`).

**Not a consumer report. Not for employment, credit, insurance, housing or tenant-screening decisions.** 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 a person's eligibility for employment, credit, insurance, housing or any other purpose covered by the Fair Credit Reporting Act or similar laws. It answers one question, whether a credential someone has presented to you is on the issuing board's register and in what status; it does not evaluate people. Verify with the issuing board. The same sentence travels with every run as the `notice` field of the `SUMMARY` record and the webhook body.

**What the Actor does not do, by design.** It has no people-search mode: a name is looked up within one board only, never across state registries, and license numbers are the primary key. It never writes a gender, ethnicity, year of birth, home or mailing street address, personal telephone number or e-mail address, even where a board publishes them. Disciplinary fields are off by default (`includeDiscipline`) and, when on, carry the board's own words without any severity or risk score; `status` and `verified` describe the credential, not the person. owlabee keeps no copy of the records: results exist only in your Apify storage, nothing is aggregated across runs or users, and no dataset is resold. The Actor is a pass-through client over government registers, not a data broker.

**Sources and terms.** Texas TDLR, Colorado DORA and Washington DOH are read through the boards' open-data APIs on Socrata (public datasets, optional free app token); Florida DBPR through its public "Verify a Licensee" page at one request per second; the NPI Registry through the CMS NPPES API. Use the data in compliance with each source's terms of use and applicable law. Not affiliated with or endorsed by TDLR, DBPR, DORA, the Washington State Department of Health or CMS; agency names are used to identify the source of the records.

# 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" (a name without a state is accepted only when exactly one source is enabled; names are never searched across states), "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 license number with no state hits every enabled source; a name with no state is accepted only when exactly one source is enabled (no cross-state name search); 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.

## `includeDiscipline` (type: `boolean`):

Off by default. When on, records carry the board's own disciplinary fields as published: disciplinaryActionFlag (Colorado DORA, Washington DOH), disciplinaryAction (Colorado case number, action and dates) and disciplinaryActionPending (Washington). Texas, Florida and NPI publish none. The Actor adds no severity or risk score. Not for employment, credit, insurance, housing or tenant-screening decisions.

## `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,
  "includeDiscipline": 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,
    "includeDiscipline": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("owlabee/license-verification-api").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,
    "includeDiscipline": False,
}

# Run the Actor and wait for it to finish
run = client.actor("owlabee/license-verification-api").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,
  "includeDiscipline": false
}' |
apify call owlabee/license-verification-api --silent --output-dataset

```

## MCP server setup

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

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/58bv5bxCeVerDn36A/openapi.json
