# Minnesota DLI Contractor License Lookup (`automation-lab/minnesota-dli-contractor-license-lookup`) Actor

Search and export official Minnesota DLI residential contractor licenses by business, number, type, status, city, and enforcement indicator.

- **URL**: https://apify.com/automation-lab/minnesota-dli-contractor-license-lookup.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.86 / 1,000 item extracteds

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

## Minnesota DLI Contractor License Lookup

Search, filter, and export **Minnesota DLI contractor licenses** from the official residential-contractor roster.

Use a business name, holder name, license number, type, status, city, enforcement indicator, or renewal indicator to turn the state CSV into integration-ready records.

The Actor is designed for contractor verification, procurement screening, compliance reviews, and scheduled status checks.

### What does Minnesota DLI Contractor License Lookup do?

The Actor downloads the public residential-contractor roster published by the Minnesota Department of Labor and Industry (DLI).

It then:

1. validates your filters;
2. downloads the current official CSV;
3. checks that the response has the expected DLI structure;
4. parses and normalizes every row;
5. applies all requested filters together;
6. deduplicates records by license number;
7. stops at your `maxItems` limit;
8. saves typed records to the default Apify dataset.

No login, browser, or proxy is required.

### Who is it for?

#### Procurement teams

Verify a contractor before onboarding or renewing an agreement.

#### Compliance and risk teams

Review license status, expiry, and DLI's enforcement-action indicator.

#### Marketplaces and property platforms

Enrich contractor profiles with official public license facts and provenance.

#### Data and operations teams

Export a repeatable snapshot to JSON, CSV, Excel, a warehouse, or an automation workflow.

#### Developers

Call one Actor endpoint instead of writing and maintaining CSV download, parsing, filtering, and normalization code.

### Why use this Actor?

- **Official source:** every row comes from DLI's public residential-contractor export.
- **Useful filters:** search by broad text or narrow fields without downloading the file yourself.
- **Typed results:** dates, booleans, contact fields, status, and provenance have stable names.
- **Bounded output:** `maxItems` controls dataset size and the number of returned license records.
- **Clear failure behavior:** an unavailable or unrecognizable source fails the run instead of pretending there are zero matches.
- **Scheduling ready:** rerun the same input to compare snapshots in your own workflow.

### What data can I extract?

| Field | Meaning |
| --- | --- |
| `holderType` | DLI's business/person classification |
| `licenseType` | Top-level license category |
| `licenseSubtype` | Specific contractor license or certificate subtype |
| `name` | Official holder name |
| `businessName` | Published DBA name |
| `addressLine1`, `addressLine2` | Published business address |
| `city`, `state`, `zipCode` | Published location |
| `phone`, `email` | Public contact values present in the roster |
| `licenseNumber` | DLI license, registration, or certificate number |
| `licenseStatus` | Status text in the current roster |
| `originalDate` | Original date, normalized to `YYYY-MM-DD` |
| `expirationDate` | Expiry date, normalized to `YYYY-MM-DD` |
| `hasEnforcementAction` | Boolean derived from DLI's enforcement indicator |
| `renewalInProgress` | Boolean derived from DLI's renewal indicator |
| `sourceUrl` | Exact official CSV URL |
| `sourceAgency` | Publishing regulator |
| `retrievedAt` | UTC retrieval timestamp |

All fields can be `null` when DLI leaves a source cell empty.

### Getting started

1. Open the Actor in Apify Console.
2. Enter one or more filters.
3. Keep `maxItems` small for your first run.
4. Click **Start**.
5. Open the **Dataset** tab.
6. Review or export the matching license rows.
7. Save the task if you want to schedule the same check.

A simple broad search:

```json
{
  "query": "CONSTRUCTION",
  "maxItems": 10
}
```

An exact lookup:

```json
{
  "licenseNumber": "XB230289",
  "maxItems": 5
}
```

An enforcement-indicator snapshot:

```json
{
  "enforcementOnly": true,
  "maxItems": 100
}
```

### Input parameters

| Input | Type | Default | Description |
| --- | --- | --- | --- |
| `query` | string | none | Substring across holder name, DBA, license number, type/subtype, and city |
| `businessName` | string | none | Substring in holder or DBA name |
| `licenseNumber` | string | none | Exact license number, case-insensitive |
| `licenseType` | string | none | Substring in type or subtype |
| `licenseStatus` | string | none | Exact DLI status |
| `city` | string | none | Exact city, case-insensitive |
| `enforcementOnly` | boolean | `false` | Keep only rows carrying DLI's enforcement indicator |
| `renewalInProgressOnly` | boolean | `false` | Keep only rows marked as renewal in progress |
| `maxItems` | integer | `100` | Save 1 to 50,000 matching records |

Filters are combined with AND logic.

For example, `city: "ROCHESTER"` and `licenseStatus: "Issued"` returns only issued records in Rochester.

`query` is also combined with every specific filter.

### Output example

A current record has this shape (values below are illustrative and anonymized):

```json
{
  "holderType": "Business",
  "licenseType": "Residential Contractors",
  "licenseSubtype": "Residential Building Contractor",
  "name": "SAMPLE CONTRACTOR LLC",
  "businessName": "SAMPLE HOME BUILDERS",
  "addressLine1": "100 SAMPLE AVE",
  "addressLine2": null,
  "city": "MINNEAPOLIS",
  "state": "MN",
  "zipCode": "55401",
  "phone": null,
  "email": null,
  "licenseNumber": "BC123456",
  "licenseStatus": "Issued",
  "originalDate": "2020-04-15",
  "expirationDate": "2027-03-31",
  "hasEnforcementAction": false,
  "renewalInProgress": false,
  "sourceUrl": "https://secure.doli.state.mn.us/ccld/data/MNDLILicRegCertExport_Residential_Contractors.csv",
  "sourceAgency": "Minnesota Department of Labor and Industry (DLI)",
  "retrievedAt": "2026-09-15T08:00:00.000Z"
}
```

### How much does it cost to look up Minnesota contractor licenses?

Pricing has two events:

- **Run started:** $0.00005 once per run.
- **License record returned:** tiered per matching dataset item.

At the BRONZE tier, a returned license record costs $0.00144.

Approximate BRONZE examples:

| Matching records | Approximate total |
| ---: | ---: |
| 1 | $0.00149 |
| 10 | $0.01445 |
| 100 | $0.14405 |
| 1,000 | $1.44005 |

An empty valid search incurs only the start fee.

The selected result cap controls the number of paid record events; infrastructure usage is not charged separately under pay-per-event pricing.

### Search and filtering tips

- Use `licenseNumber` for the narrowest verification.
- Use `businessName` when punctuation or spacing may differ from your internal record.
- Use `query` for discovery across several identity fields.
- Status values in DLI data include Issued, Expired, Revoked, Suspended, Voluntary Termination, and Withdrawn.
- Use `licenseType` with phrases such as `Residential Building Contractor`, `Qualifying Builder`, or `Residential Roofer Contractor`.
- Combine `enforcementOnly` with another filter for focused recurring checks.
- Increase `maxItems` only when you need more matches.

### Enforcement and renewal indicators

`hasEnforcementAction` reports whether DLI's published row contains the enforcement-action flag.

It does not describe the action, allegation, order, or case outcome.

`renewalInProgress` reports the roster's renewal marker.

Neither boolean should be interpreted as legal advice or as a complete regulatory history.

Follow the official source and DLI guidance when a decision requires case-level context.

### Recurring verification workflow

1. Create an Apify Task with a stable license or supplier filter.
2. Schedule it daily, weekly, or before a procurement checkpoint.
3. Send results to your preferred storage or automation.
4. Compare `licenseStatus`, `expirationDate`, `hasEnforcementAction`, and `renewalInProgress` with your previous snapshot.
5. Route material changes to a human reviewer.

The Actor returns the current roster snapshot; it does not maintain history or send alerts by itself.

### Export and integrations

The default dataset supports:

- JSON and JSONL;
- CSV and Excel;
- XML and RSS;
- direct API reads;
- Make integrations;
- Zapier integrations;
- webhooks;
- Google Sheets workflows;
- cloud warehouse loaders.

For a CRM or vendor database, use `licenseNumber` as the stable join key when it is present.

### Run through the API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~minnesota-dli-contractor-license-lookup/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"businessName":"CONSTRUCTION","licenseStatus":"Issued","maxItems":25}'
```

To wait for results in one request:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~minnesota-dli-contractor-license-lookup/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"licenseNumber":"XB230289","maxItems":5}'
```

### Run through the API with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/minnesota-dli-contractor-license-lookup').call({
  enforcementOnly: true,
  maxItems: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run through the API with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/minnesota-dli-contractor-license-lookup').call(
    run_input={'city': 'DULUTH', 'licenseStatus': 'Issued', 'maxItems': 100}
)
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with MCP and AI agents

Add the Apify MCP server to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/minnesota-dli-contractor-license-lookup"
```

#### Claude Desktop

Add this HTTP MCP server in your Claude Desktop configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/minnesota-dli-contractor-license-lookup"
    }
  }
}
```

#### Cursor

Add the same `apify` server URL under **Settings → MCP** in Cursor.

#### VS Code

Add the same HTTP MCP server URL to your workspace MCP configuration in VS Code.

Example prompts:

- “Verify Minnesota DLI contractor license XB230289 and summarize its status and expiry.”
- “Return up to 50 issued residential building contractors in Duluth.”
- “Export 20 Minnesota contractor rows carrying DLI's enforcement indicator.”

### Source freshness and reliability

Each run downloads the roster currently available at DLI's official URL.

The Actor does not cache a private copy between runs.

DLI controls publication timing, corrections, outages, and field coverage.

Transient network errors, rate limits, and server errors are retried up to three times with bounded backoff.

If the response is unavailable or no longer resembles the expected CSV, the run fails visibly.

### Limitations

- Coverage is the DLI **residential contractor** roster, not every Minnesota occupation or trade.
- Electrical, plumbing, boiler, and other rosters are outside this Actor's scope.
- The source may contain historical expired certificates as well as current records.
- Public contact fields can be blank.
- Search is textual and case-insensitive; it does not perform fuzzy identity resolution.
- Enforcement output is only DLI's indicator, not enforcement case details.
- The Actor returns a current snapshot and does not calculate changes from previous runs.
- Broad unfiltered output is capped by `maxItems`.

### Failure behavior and troubleshooting

#### My run returns zero records

Check spelling, remove one filter, or use `query` for a broader substring search.

Remember that specific filters are combined with AND logic.

#### My status filter returns less than expected

Try removing the status filter and inspect the current text published by DLI.

#### The run fails while downloading

Inspect the run log for the HTTP status. A DLI outage or changed CSV structure intentionally causes failure rather than unreliable empty output.

Retry later after confirming the official source is reachable.

#### Dates or contact values are null

Those cells are blank in the official roster. The Actor does not infer or enrich missing government data.

#### Should I enable a proxy?

No proxy input is needed. The source is a public downloadable file and the Actor uses direct HTTPS.

### Responsible use and legality

The Actor processes publicly accessible professional and business license data published by a government regulator.

Use it for legitimate verification, compliance, research, and data-quality purposes.

Follow applicable privacy, anti-discrimination, procurement, records-retention, and consumer-reporting rules.

Do not treat one automated field as a final eligibility or legal conclusion.

Verify material decisions against the linked official source and provide appropriate human review.

### Related Automation Lab Actors

- [California Contractor License Lookup](https://apify.com/automation-lab/california-contractor-license-lookup-v2) for California CSLB contractor records.
- [North Carolina Contractor License Lookup](https://apify.com/automation-lab/north-carolina-contractor-license-lookup-scraper) for North Carolina general contractors.
- [Chicago Business Licenses Scraper](https://apify.com/automation-lab/chicago-business-licenses) for Chicago business-license records.

Choose this Actor when your source of truth is Minnesota DLI's residential-contractor roster.

### FAQ

#### Is this an official Minnesota government service?

No. It is an independent Apify Actor that reads and links to DLI's official public data.

#### Does it verify all Minnesota professional licenses?

No. It is intentionally limited to the residential-contractor export.

#### Can I download the whole roster?

Yes. Run without filters and set `maxItems` high enough, up to 50,000. The source currently contains more rows than that, so the Actor's documented cap still applies.

#### Can I search by a person's name?

Yes. Use `businessName` to search both official holder and DBA names, or use `query` to search those fields plus license, type, and city.

#### Are email and phone always available?

No. They are returned only when DLI publishes them in the row.

#### Does an enforcement flag mean the license is currently revoked?

Not necessarily. Treat the indicator and status as separate source fields and review official case information for any consequential decision.

#### Are no-result runs charged?

A valid no-result run incurs the one-time start event but no per-record events.

#### Can I schedule it?

Yes. Save the input as an Apify Task, add a schedule, and compare each dataset with your own previous snapshot.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/minnesota-dli-contractor-license-lookup/changelog.md

# Actor input Schema

## `query` (type: `string`):

Case-insensitive text matched across holder name, DBA, license number, license type/subtype, and city.

## `businessName` (type: `string`):

Case-insensitive substring matched against the official holder name and DBA name.

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

Exact Minnesota DLI license number, matched without case sensitivity (for example, XB230289).

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

Case-insensitive substring matched against license type and subtype, such as Residential Building Contractor or Qualifying Roofer.

## `licenseStatus` (type: `string`):

Exact status from the DLI roster, matched without case sensitivity.

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

Exact business city from the DLI roster, matched without case sensitivity.

## `enforcementOnly` (type: `boolean`):

Return only licenses whose official DLI row has the enforcement-action indicator set.

## `renewalInProgressOnly` (type: `boolean`):

Return only licenses whose official DLI row says renewal is in progress.

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

Maximum matching license records to save. The official roster is scanned once and output stops at this limit.

## Actor input object example

```json
{
  "query": "CONSTRUCTION",
  "enforcementOnly": false,
  "renewalInProgressOnly": false,
  "maxItems": 10
}
```

# Actor output Schema

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

Dataset containing normalized contractor licenses and official source provenance.

# 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 = {
    "query": "CONSTRUCTION",
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/minnesota-dli-contractor-license-lookup").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 = {
    "query": "CONSTRUCTION",
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/minnesota-dli-contractor-license-lookup").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 '{
  "query": "CONSTRUCTION",
  "maxItems": 10
}' |
apify call automation-lab/minnesota-dli-contractor-license-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/minnesota-dli-contractor-license-lookup"
        }
    }
}
```

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/KOLv07Wcs48mrd91D/builds/fIMrQ5uIefBFoDQey/openapi.json
