# Delaware Professional Licenses & Discipline (`j0401/de-professional-licenses`) Actor

Delaware professional and occupational licences (public data, 353,905 licences across 38 professions) plus the state's disciplinary actions against licensees (8,483 sanctions) - searchable by name, profession, licence type, status or action taken.

- **URL**: https://apify.com/j0401/de-professional-licenses.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.03 / 1,000 de license or disciplinary 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

## Delaware Professional Licenses & Discipline

### Low cost

**From $0.00005 per record, down to $0.00003 at Gold** - pay per record delivered, and nothing for the query. Each record costs a fraction of a cent.

Delaware's professional and occupational licensing register, and the disciplinary actions the state has taken against the people on it.

### What you get

Two corpora over the same licensee universe, linked by licence number.

| Corpus | Rows | What it holds |
|---|---|---|
| **licenses** (default) | 353,905 | one row per licence: licensee name, profession, licence type, status, city / state / ZIP, issue and expiration dates |
| **disciplinary** | 8,483 | one row per sanction: the action taken, its start and end dates, with the licensee's name, profession and licence type |

38 professions - Nursing (~90k), Medical Practice (~38k), Charitable Gaming (~33k), Real Estate (~31k), Electrical Examiners (~27k) and more. 297 licence types. 21 disciplinary action types, from Letter of Reprimand and Fine through Suspension, Probation and Revocation.

### Discipline is the real record, not a column

The register carries a `disciplinary_action` field. It reads `N` on all 353,905 rows - every single one. It looks like a screening flag and is in fact a constant, so it is not exposed here at all.

The state's actual disciplinary record lives in a separate dataset: 8,483 sanctions with the action taken and its dates. Both carry the licence number, and that link resolves **4,320 of 4,322** licences (99.95%). One run returns one corpus, so a licensee's licence record and their sanction history take two queries against that shared number - about a fifth of a cent.

This is worth stating plainly because the column being 100% populated makes it look like a feature. It is the opposite.

### Modes

- **licenses** (default) - the register, newest licence first
- **disciplinary** - the sanction record, newest action first
- **aggregate** - one count row per group: by profession, licence type, status or state (licences), or by action type (disciplinary)

Filter the register by name, profession, licence type, status, city, ZIP, or issue / expiration window. Filter the sanction record by name, profession, licence type, action, or action-date window. Using a filter the chosen corpus does not have is rejected outright rather than quietly returning unfiltered rows.

### Example inputs

**One licence by number** - `licenseNumber` works in both corpora.

```json
{ "licenseNumber": "L1-0077557" }
```

**Active nurses in one ZIP** - `profession` and `zip` are both substring matches.

```json
{
  "profession": "Nursing",
  "status": "Active",
  "zip": "199",
  "maxResults": 25
}
```

**The sanctions the state has issued** - switch corpus; `action` is a substring match.

```json
{ "corpus": "disciplinary", "action": "Revocation", "maxResults": 25 }
```

**The register by profession** - `aggregate=true` returns one count row per group, every group of the dimension.

```json
{ "aggregate": true, "groupBy": "profession" }
```

### Why this is hard

**The status vocabulary is 17 values and several overlap.** `Suspended` is a substring of `Non-Disciplinary Suspension`; `Expired` and `Expired Inactive` are different states; `Terminated`, `Deactivated`, `Annulled` and `Withdrawn` each mean a licence ended in a different way, and `Denied` means one was never granted. A filter that matches loosely returns the wrong licensees - for a licence-verification product that is the whole ballgame, so statuses match exactly.

**The dates are not all plausible.** Issue dates run back to 1900 and expiry dates out to 2206. But a licence legitimately expiring in 2030 is *normal* - 8,645 of them sit more than two years out - so clearing junk on a tight window would delete real licences instead. Only the impossible outliers are cleared.

**And the sanctions are rows, not a flag.** One licensee can carry several actions, sometimes on the same day - a Letter of Reprimand and a Fine are two rows for one person. Counting rows counts sanctions, not people, and the register's own flag cannot tell you either.

### Output

One schema across both corpora: every record carries the full field set, empty where that corpus has nothing to say, so the same downstream code reads either. Dates arrive as `YYYY-MM-DD`, and each record carries `sourceUpdatedAt`, the register's own last-refresh timestamp.

### Example output

**One licence** - `licenseNumber=L1-0077557` returns the licence record:

```json
{
 "action": "",
 "actionEndDate": "",
 "actionStartDate": "",
 "city": "BETHEL",
 "corpus": "licenses",
 "country": "United States",
 "expirationDate": "2027-09-30",
 "firstName": "SEAN",
 "fullName": "COLLINS,SEAN",
 "issueDate": "2026-09-23",
 "lastName": "COLLINS",
 "licenseNumber": "L1-0077557",
 "licenseType": "Registered Nurse",
 "profession": "Nursing",
 "sourceUpdatedAt": "2026-09-24T10:33:51Z",
 "state": "DE",
 "status": "Active",
 "zip": "19931",
 "platform": "de-professional-licenses",
 "source": "de-delaware-professional-licensing",
 "groupKey": "",
 "groupCount": "",
 "groupBy": ""
}
```

**`aggregate=true`, `groupBy=profession`** - one count row per profession:

```
Nursing                       89,973
Medical Practice              37,726
Charitable Gaming             32,565
Real Estate                   31,000
Electrical Examiners          27,035
Cosmetology and Barbering     24,948
```

### Notes

- Public open data from the State of Delaware. No login, no scraping.
- Charges are metered per record delivered, so one licensee's licence and full sanction history costs a fraction of a cent.

# Actor input Schema

## `corpus` (type: `string`):

licenses (default) = the register, one row per licence. disciplinary = the state's disciplinary actions, one row per sanction (a licensee with several sanctions has several rows).

## `licenseNumber` (type: `string`):

Licence number substring, e.g. L2-0012651. Works in both modes - use it to pull a licensee's licence record and their full sanction history together.

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

Licensee name substring, case-insensitive. Matches the last name, first name or full name.

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

Profession substring, e.g. Nursing, Medical Practice, Real Estate, Pharmacy, Cosmetology and Barbering, Electrical Examiners, Accountancy.

## `licenseType` (type: `string`):

Licence-type substring, e.g. Registered Nurse, Physician M.D., Salesperson, Licensed Practical Nurse, Cosmetologist.

## `status` (type: `string`):

Licence mode only. A source status exactly: Active, Expired, Closed, Deceased, Inactive, Deactivated, Terminated, Suspended, Revoked, Annulled, Probation, Expired Inactive, Delinquent, Non-Disciplinary Suspension, Withdrawn, Denied, Under Review.

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

Licence mode only. Licensee city substring, case-insensitive.

## `zip` (type: `string`):

Licence mode only. Licensee ZIP prefix, e.g. 198 or 19901.

## `issuedFrom` (type: `string`):

Licence mode only. Only licences issued on/after this date (YYYY-MM-DD).

## `issuedTo` (type: `string`):

Licence mode only. Only licences issued on/before this date (YYYY-MM-DD).

## `expiresFrom` (type: `string`):

Licence mode only. Only licences expiring on/after this date (YYYY-MM-DD). Combine with status=Active to find licences coming up for renewal.

## `expiresTo` (type: `string`):

Licence mode only. Only licences expiring on/before this date (YYYY-MM-DD).

## `action` (type: `string`):

Disciplinary mode only. Action substring, e.g. Letter of Reprimand, Fine, Remedial Education, Suspension, Probation, Revocation, Cease and Desist Order, Voluntary Surrender, Limitation.

## `from` (type: `string`):

Disciplinary mode only. Only actions starting on/after this date (YYYY-MM-DD).

## `to` (type: `string`):

Disciplinary mode only. Only actions starting on/before this date (YYYY-MM-DD).

## `aggregate` (type: `boolean`):

When on, returns one summary record per group (see groupBy) with count - instead of individual licences or actions.

## `groupBy` (type: `string`):

Which dimension to aggregate over. Blank picks the first dimension that suits the chosen corpus. licences: profession / licenseType / status / state. disciplinary: action / profession / licenseType.

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

Cap the number of records pushed (0 = up to ~10k per run; each record is metered individually, so there is no per-run charge cap). Does not apply in aggregate mode, which returns every group of the chosen dimension.

## Actor input object example

```json
{
  "corpus": "licenses",
  "licenseNumber": "",
  "name": "",
  "profession": "",
  "licenseType": "",
  "status": "",
  "city": "",
  "zip": "",
  "issuedFrom": "",
  "issuedTo": "",
  "expiresFrom": "",
  "expiresTo": "",
  "action": "",
  "from": "",
  "to": "",
  "aggregate": false,
  "groupBy": "",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Delaware professional-license records, disciplinary actions or aggregates - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("j0401/de-professional-licenses").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("j0401/de-professional-licenses").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 '{}' |
apify call j0401/de-professional-licenses --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/de-professional-licenses"
        }
    }
}
```

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/hbTFpFiMWT2NJ5oxl/builds/acbizEkTDW8TdHhji/openapi.json
