Minnesota DLI Professional License Scraper
Pricing
from $1.20 / 1,000 results
Minnesota DLI Professional License Scraper
Minnesota DLI license scraper & API: search Department of Labor and Industry licenses and export license number, type, status, holder, business, address and expiry. License verification, compliance and B2B lead generation — fast, no login.
Pricing
from $1.20 / 1,000 results
Rating
0.0
(0)
Developer
Haketa
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
7 days ago
Last modified
Categories
Share
Minnesota DLI Professional License Scraper — Contractor, Electrician, Plumber, Roofer & Boiler License Data Extractor
The most complete Minnesota Department of Labor and Industry (DLI) license data extraction tool on Apify. Download the full public regulant export for residential contractors, electricians, plumbers, boiler operators, elevator constructors, high-pressure pipefitters, contractor registrations, and mechanical contractor bond holders — structured, normalized, and ready for compliance, B2B sales, recruiting, credentialing, and analytics workflows across Minneapolis, Saint Paul, Rochester, Duluth, and every other Minnesota market.
What This Actor Does
The Minnesota DLI Professional License Scraper is a production-grade Apify Actor that extracts the complete public regulant database published by the Minnesota Department of Labor and Industry (DLI) — the state agency that licenses contractors, registers tradespeople, and tracks bond and enforcement records across Minnesota's construction, mechanical, and building trades industries.
In a single run (typically 30–90 seconds for the full ZIP), the actor returns hundreds of thousands of structured records spanning eight distinct license categories that DLI publishes as bulk CSV exports — no browser, no login, no API keys, no captcha solving. Each record is normalized into a single flat JSON schema so downstream consumers (warehouses, CRMs, BI tools) can ingest the entire dataset without per-category branching logic.
The eight Minnesota DLI license categories covered:
- Electrical — Class A/B masters, journeyworkers, installers, power-limited technicians, electrical contractors (~110,000 records, the largest category)
- Plumbing — master, journeyworker, and restricted plumbers plus plumbing contractors (~37,000 records)
- Residential Contractors — Residential Building Contractor (RBC) and Residential Remodeler (RR) licenses (~59,000 records)
- Contractor Registrations — registered (non-licensed) construction contractors using the residential-exemption pathway (~21,000 records)
- Mechanical Contractor Bond — surety bond filings for mechanical / HVAC contractors (~5,400 records)
- Boiler — operator, inspector, and special pressure-vessel credentials (~35,000 records)
- High Pressure Piping — pipefitter and pipefitting contractor licenses (~7,500 records)
- Elevator — elevator constructor mechanics, limited mechanics, and elevator contractors (~1,900 records)
Across all eight categories the live export carries roughly 275,000+ records (active plus expired/inactive history). Each record carries license number, license type and subtype, status, original issue date, expiration date, full mailing address, entity classification (Business vs. Personal), DBA, phone (where the licensee provided one), and enforcement-action flags — making this the fastest path to a clean Minnesota tradesperson and contractor dataset for compliance, lead generation, credentialing, and market intelligence use cases.
Why scrape DLI yourself when this exists?
DLI publishes the raw data as eight separate CSV files (plus one combined ZIP) on secure.doli.state.mn.us. Most teams who try the DIY route hit the same wall of papercuts:
- The
Electricalfile alone is ~110,000 rows and the combined dataset is ~275,000 rows — enough that naive one-shot parsing in a notebook stalls or runs out of memory - A handful of rows contain unescaped embedded quotes in business names — one malformed row aborts a naive
csvparse and silently zeroes out the entire category - Header casing can drift between exports (
Lic_NumbervsLic_NovsLicense_Number) and breaks string-keyed parsers - The
Bus_Persfield is a single-letter code (B/P) that must route Name into eitherbusinessNameorindividualName Addr1+Addr2come as separate columns and must be merged; ZIPs ship with a-0000suffix that needs stripping- Date columns use
MM/DD/YYYYand must be normalized to ISOYYYY-MM-DDfor warehouse ingestion - No incremental update endpoint exists — you must redownload the full export every run
- A category endpoint will occasionally return an HTML error page instead of a CSV — homegrown parsers choke on the mismatch
- The official site is HTTPS-only with a strict TLS profile that trips older Python
requestssetups
This actor solves all of that: it streams the CSVs (or the combined ZIP) directly, applies a 100+ entry header alias table to survive schema drift, tolerates malformed rows (skipping only the bad record instead of the whole file), detects HTML error responses and skips them cleanly, normalizes dates and ZIPs, routes Name into the correct field by entity type, applies your filters, and outputs deterministic JSON — no parsing scripts, no schema babysitting, no glue code.
Quick Start
One-Click Run
- Click "Try for free" on the Apify Store page for
haketa/minnesota-dli-license-scraper - Pick specific categories (prefilled with
Mechanical_Contractor_BondandHigh_Pressure_Piping) or tick Include All Categories to grab everything in one shot - Hit Start — full export ready in under two minutes
- Download as JSON, CSV, Excel, or HTML from the Apify dataset view, or stream to Google Sheets / BigQuery / your CRM via built-in integrations
API Run (Python)
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("haketa/minnesota-dli-license-scraper").call(run_input={"categories": ["Residential_Contractors", "Plumbing", "Electrical"],"statusFilter": "issued","maxRecords": 0})for record in client.dataset(run["defaultDatasetId"]).iterate_items():print(record["licenseNumber"], record["name"], record["city"], record["status"])
API Run (Node.js / TypeScript)
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('haketa/minnesota-dli-license-scraper').call({useZip: true,statusFilter: 'issued',maxRecords: 0,});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(`Got ${items.length} active Minnesota DLI license records`);const minneapolis = items.filter(r => (r.city || '').toLowerCase() === 'minneapolis');console.log(` of which ${minneapolis.length} are in Minneapolis`);
API Run (cURL)
curl -X POST "https://api.apify.com/v2/acts/haketa~minnesota-dli-license-scraper/runs?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"categories": ["Mechanical_Contractor_Bond"],"statusFilter": "issued","maxRecords": 0}'
How It Works
DLI publishes eight category-specific CSV files plus a single combined ZIP containing all eight, refreshed continuously as licenses are issued, renewed, expired, suspended, or revoked. This actor downloads them directly over HTTPS — no headless browser, no Puppeteer, no Playwright, no API key.
| Source File | URL Pattern | Coverage |
|---|---|---|
MNDLILicRegCertExport_Electrical.csv | https://secure.doli.state.mn.us/ccld/data/MNDLILicRegCertExport_Electrical.csv | Electrical contractors, masters, journeyworkers, installers, PLTs |
MNDLILicRegCertExport_Plumbing.csv | same path with _Plumbing | Master/journeyworker/restricted plumbers, plumbing contractors |
MNDLILicRegCertExport_Residential_Contractors.csv | ..._Residential_Contractors | RBC, RR licenses |
MNDLILicRegCertExport_Contractor_Registrations.csv | ..._Contractor_Registrations | Registered (non-licensed) construction contractors |
MNDLILicRegCertExport_Mechanical_Contractor_Bond.csv | ..._Mechanical_Contractor_Bond | Mechanical/HVAC contractor bond filings |
MNDLILicRegCertExport_Boiler.csv | ..._Boiler | Boiler operators, inspectors, special licenses |
MNDLILicRegCertExport_High_Pressure_Piping.csv | ..._High_Pressure_Piping | High-pressure pipefitters, pipefitting contractors |
MNDLILicRegCertExport_Elevator.csv | ..._Elevator | Elevator constructors, limited mechanics, contractors |
MNDLILicRegCertExport.zip | https://secure.doli.state.mn.us/ccld/data/MNDLILicRegCertExport.zip | Combined archive of all eight files |
Engineering details
- Direct HTTPS GET via
got-scrapingwith a desktop Chrome User-Agent — no headless browser, no API key - Two ingestion modes — individual category CSVs (
categories) or combinedMNDLILicRegCertExport.zip(useZip: true) - ZIP extraction via
unzipper— archive streamed to/tmp, expanded, each CSV parsed in sequence - 3-attempt retry with linear backoff (2 s, 4 s) on every download
- 100+ entry HEADER_ALIASES map absorbs DLI's column-name drift (
Lic_Number,License_Number,Lic_No,Lic_Nbr,Registration_Number,Certificate_Numberall map tolicenseNumber) - Malformed-row tolerance —
skip_records_with_errordrops only the rare rows with unescaped embedded quotes (e.g. a business name likePlayNo"Gamez"Demo) instead of aborting the whole-file parse and zeroing the category - HTML-response guard — if a category endpoint returns an error page instead of a CSV, the actor detects the
<!doctype html>head and skips it cleanly rather than throwing a cryptic parse error - Entity-type routing —
Bus_Pers=B→businessName;P→individualName; mergednamepreserved as convenience - Address merging —
Addr1+Addr2joined with a comma; ZIP normalization strips trailing-0000; date normalization coercesMM/DD/YYYY,YYYY/MM/DD,YYYY-MM-DDto ISO - Status filter —
issuedkeeps active licenses only;allkeeps every status - 200-record batched writes keep memory flat even on the full ~275K-record export
- Graceful partial-save on failure — if DLI hiccups mid-run, every record collected so far is committed before the actor exits non-zero
Pagination & Scale
There is no pagination to worry about. DLI exposes each license category as a single bulk file (one CSV per category, or one combined ZIP containing all eight), and the actor downloads the complete file in one request — so you get every record in a category, not a page of them. This is what makes a full-state scrape so fast and cheap.
- Full dataset size: roughly 275,000 records across the eight categories (Electrical alone is ~110,000 rows; Boiler ~35,000; Residential Contractors ~59,000; Plumbing ~37,000; Contractor Registrations ~21,000; High Pressure Piping ~7,500; Mechanical Contractor Bond ~5,400; Elevator ~1,900). Counts drift continuously as licenses are issued, renewed, and expired.
maxRecordscaps the total number of records written across all selected categories (0= unlimited / full export). On individual-CSV mode the cap is applied as records stream in, and once it is reached the actor stops downloading further categories.- Bare-run sample mode: a run with no categories, no
useZip, and no positivemaxRecordsautomatically returns a fast ~100-record mixed sample spread across all eight categories, so a first-time{}run stays cheap and representative rather than pulling all 275K rows. - De-duplication: DLI's exports are already de-duplicated by license number. A business that holds licenses in several categories appears once per category (each a distinct license record), keyed by
licenseNumber+sourceCategory.
Input Parameters
{"categories": ["Mechanical_Contractor_Bond", "High_Pressure_Piping"],"useZip": false,"statusFilter": "all","maxRecords": 0,"requestDelay": 200,"proxyConfiguration": { "useApifyProxy": false }}
Parameter reference
| Parameter | Type | Default | Description |
|---|---|---|---|
categories | array<string> | [] (= all 8) | DLI license categories to scrape — one CSV download per category. Allowed values: Electrical, Plumbing, Residential_Contractors, Contractor_Registrations, Mechanical_Contractor_Bond, Boiler, High_Pressure_Piping, Elevator. Leave empty to scrape all categories. |
useZip | boolean | false | When true, downloads the single combined MNDLILicRegCertExport.zip archive and extracts all eight CSVs in one round trip. Overrides categories. Recommended for full-export jobs. |
statusFilter | string | all | issued returns only currently active/issued licenses. all returns every record including expired, revoked, suspended, surrendered, and retired. |
maxRecords | integer | 0 (unlimited) | Cap total output across all selected categories. 0 = unlimited (full export). A bare run with no categories, no ZIP flag, and no positive cap automatically returns a fast ~100-record mixed sample so first-time test runs stay cheap. Set a positive value or 0 for production scrapes. |
requestDelay | integer | 200 | Milliseconds to sleep between category downloads in individual-CSV mode. Has no effect in ZIP mode. |
proxyConfiguration | object | { useApifyProxy: false } | Optional Apify proxy settings. Almost never needed — DLI does not rate-limit or geo-block its public file downloads. |
Output Schema
Every record — regardless of which DLI category it came from — uses the same flat JSON schema. Core identity, status, date, and address fields are present on essentially 100% of records. Contact and category-specific fields (phone, email, dba, bondAmount, bondCompany) are present only when the underlying CSV publishes them (see the "Always Present" column below for realistic coverage).
Field reference
| Field | Type | Always Present | Description |
|---|---|---|---|
licenseNumber | string | ~100% | DLI-issued license, registration, or certificate number — typically a 2-letter category prefix plus digits |
licenseType | string | ~100% | License type as reported by DLI (e.g., Electrical Contractor, Master Plumber) |
licenseSubtype | string | ~100% | Specialty or sub-class (e.g., Class A, Restricted, Limited) |
sourceCategory | string | yes | One of the eight file categories — Electrical, Plumbing, etc. — useful for downstream routing |
entityType | string | yes | Normalized Business or Personal (derived from DLI's single-letter B/P code) |
businessName | string | when entityType=Business | Firm / company name |
individualName | string | when entityType=Personal | Person name |
name | string | ~100% | Convenience merged name field (business name or individual name) |
dba | string | ~5% (data-dependent) | Doing-Business-As name — only where a business operates under a different name |
responsibleIndividual | string | optional | Qualifying person on file (populated only on category files that publish this column) |
status | string | ~100% | Raw status (Issued, Expired, Revoked, Suspended, Surrendered, etc.) |
issueDate | string | ~100% | Original issue date in YYYY-MM-DD format |
expirationDate | string | ~100% | Current expiration date in YYYY-MM-DD format |
address | string | ~100% | Full mailing address (Addr1 + Addr2 merged) |
city | string | ~100% | City |
state | string | ~100% | Two-letter US state abbreviation |
zipCode | string | ~100% | ZIP code (-0000 suffix stripped) |
county | string | optional | Minnesota county name (populated only on category files that publish this column) |
phone | string | ~85–90% (data-dependent) | Contact phone number — present where the licensee provided one |
email | string | present when available | Contact email. The DLI public export currently ships this column empty across categories, so it is almost always null — do not rely on it as a contact channel |
bondAmount | string | Mechanical bond filings (when published) | Surety bond face value — populated only if DLI exposes a bond-amount column |
bondCompany | string | Mechanical bond filings (when published) | Issuing surety/bond company — populated only if DLI exposes a surety column |
enforcementAction | string | ~100% | Enforcement-action flag (0 = none on file) |
renewalInProgress | string | optional | Indicator that a renewal is currently being processed |
scrapedAt | string | yes | ISO-8601 timestamp of extraction |
Example: Residential Building Contractor (Business)
{"licenseNumber": "BC999999","licenseType": "Residential Building Contractor","licenseSubtype": "BC","sourceCategory": "Residential_Contractors","entityType": "Business","businessName": "NORTHERN LIGHTS BUILDERS LLC","individualName": null,"name": "NORTHERN LIGHTS BUILDERS LLC","dba": "NORTHERN LIGHTS HOMES","responsibleIndividual": null,"status": "Issued","issueDate": "2018-04-12","expirationDate": "2027-03-31","address": "1234 NICOLLET AVE, SUITE 200","city": "Minneapolis","state": "MN","zipCode": "55403","county": null,"phone": "6125550100","email": null,"bondAmount": null,"bondCompany": null,"enforcementAction": "0","renewalInProgress": null,"scrapedAt": "2026-05-16T09:00:00.000Z"}
Note:
county,responsibleIndividual,bondAmount, andbondCompanyarenullabove because the current DLI export does not publish those columns. They remain in the schema so your downstream table stays stable if DLI adds them later.
Example: Master Plumber (Personal) — abbreviated
{"licenseNumber": "PM099999","licenseType": "Master Plumber","sourceCategory": "Plumbing","entityType": "Personal","individualName": "JOHNSON MARK D","name": "JOHNSON MARK D","status": "Issued","issueDate": "2009-06-30","expirationDate": "2026-09-30","address": "789 GRAND AVE","city": "Saint Paul","state": "MN","zipCode": "55105","phone": "6515550123","scrapedAt": "2026-05-16T09:00:00.000Z"}
Example: Mechanical Contractor Bond — abbreviated
{"licenseNumber": "MB804743","licenseType": "Mechanical Contractor Bond","licenseSubtype": "Mechanical Contractor Bond","sourceCategory": "Mechanical_Contractor_Bond","entityType": "Business","businessName": "TWIN CITIES HVAC SERVICES LLC","name": "TWIN CITIES HVAC SERVICES LLC","status": "Issued","issueDate": "2023-03-10","expirationDate": "2027-03-10","address": "260 LINCOLN AVE","city": "Rochester","state": "MN","zipCode": "55901","phone": "5075550177","enforcementAction": "0","scrapedAt": "2026-05-16T09:00:00.000Z"}
License Status Reference
DLI's status column carries these canonical values. Use statusFilter: "issued" to keep only the first row; use all for everything.
| Status | Practice/Operate? | Meaning |
|---|---|---|
Issued | Yes | License is currently active and in good standing |
Expired | No | Failed to renew by the deadline; may be reinstated |
Revoked | No | Terminated by Board action |
Suspended | No | Temporarily barred from practice |
Surrendered | No | Voluntarily relinquished (often pre-disciplinary) |
Retired | No | Retired credential — no longer practicing |
Cancelled | No | Administratively cancelled |
Pending | Conditional | Application in progress |
Tip: When
statusFilteris set toissued, the actor performs a case-insensitive exact match onIssued. All other statuses are filtered out and tallied in thetotalSkippedlog line.
Use Cases
Construction & Trades B2B Lead Generation
Suppliers, distributors, software vendors, and equipment rental companies use the Minnesota DLI dataset to build clean targeted lead lists for the Upper Midwest construction market:
- Pull every active Residential Building Contractor in the Twin Cities metro for a roofing-material or siding catalog mail drop
- Pull every active Mechanical Contractor Bond holder as a focused HVAC / mechanical firm list for distributor onboarding
- Build territory routes by joining on city and ZIP across Hennepin, Ramsey, Olmsted, Dakota, Anoka, and Washington markets
- Enrich CRM accounts (Salesforce, HubSpot, Pipedrive) with current license status, expiration, and responsible-individual fields keyed off the DLI license number
- Target license-class upgrades — sell exam prep, CE, or insurance to journeyworkers approaching the master's exam window
Construction Compliance, Credentialing & Verification
GCs, developers, property managers, REITs, and GL insurers in Minnesota use DLI data to enforce credential rules on every job:
- Pre-bid verification of every sub's electrical, plumbing, HVAC, or roofing license before awarding work
- Automate monthly re-checks for every subcontractor on the master vendor list — flip a record from
IssuedtoExpiredand trigger a procurement alert - Capture
enforcementActionflags so a sub with a fresh administrative penalty surfaces in the next compliance review - Build audit-ready logs with the timestamped
scrapedAtfield for OSHA, MSHA, or insurance carrier audits - Confirm a mechanical contractor carries an active bond filing at policy bind by checking the Mechanical Contractor Bond roster for a current
Issuedrecord
Insurance Underwriting (Contractor & Surety)
GL carriers, surety bond agencies, and workers comp carriers writing Minnesota contractor policies use DLI data to:
- Verify license validity at quote, bind, and annual renewal
- Underwrite residential contractor and remodeler policies using the Residential Contractors roster (critical in hail-prone MN, where roofing and remodel work drives property loss)
- Cross-check the Mechanical Contractor Bond roster against the surety's own book to find book-of-business overlap
- Monitor portfolio risk — flag insureds whose status changes mid-policy from
IssuedtoSuspendedorRevoked - Process claims faster with pre-verified licensee information including license number, type, and current status
Recruiting, Staffing & Trade Workforce Analytics
Tradesperson recruiters, union halls, and apprenticeship programs use the DLI roster as the closest thing Minnesota has to a complete licensed-trade workforce census:
- Source candidates for journeyworker electrician, plumber, or pipefitter openings by city or county
- Map apprentice-to-journeyworker conversion by tracking individuals across years of snapshots
- Quantify trade workforce supply for IBEW, UA, Sheet Metal, and other local trade unions
- Identify retired or expired credentials as a re-engagement pool for back-to-work programs
Real Estate & Property Development Due Diligence
Homebuyers, real estate attorneys, title insurers, and home inspectors use DLI data to verify the people who worked on a property:
- Validate that the contractor on a permit was licensed at the date the work occurred (combined with archived run snapshots)
- Identify the responsible qualifying individual behind an LLC contractor that has since dissolved
- Run pre-purchase verification on the roofers and remodelers who touched the home — critical in hail-prone Minnesota where roof age and roofer credentials drive insurability
- Support construction-defect litigation discovery with full license history across multiple categories
Market Research, Competitive Intelligence & M&A
Private equity firms and management consultants use DLI data to size and segment Minnesota's construction market:
- Quantify total active residential contractor count in MN over time as a leading indicator for housing-starts demand
- Build target lists for HVAC, plumbing, or electrical roll-up plays segmented by category, entity type, and geography
- Track new-license issuance rates as an inflow indicator for the regional trades labor pool
- Benchmark license-density per capita across MN metros — Twin Cities vs. greater Minnesota
Government, Regulatory Research & Investigative Journalism
State legislators, academic policy researchers, and investigative reporters use DLI data to study Minnesota's construction-industry regulation:
- Investigate enforcement-action patterns — which categories see the most discipline?
- Map license density vs. housing permit volume to study under- or over-supply of trades labor
- Track post-disaster contractor influx — out-of-state roofer registrations spike after major hail events
- Cover unlicensed contracting by joining DLI records to municipal permit databases and flagging the gap
Marketing, Direct Mail & Litigation
Trade media, direct-mail houses, attorneys, and forensic experts also tap the dataset to:
- Build print-distribution lists for trade magazines targeting MN plumbers, electricians, or HVAC techs
- Power trade-show invite mailings for IBS, AHR Expo, NECA, MCAA regional events
- Verify expert witness credentials before engagement
- Build chronologies of an individual or firm's license history across snapshots
- Pull complete public address-of-record history for service-of-process / subpoena prep
Sample Queries & Recipes
Recipe 1: All active residential contractors statewide (lead list)
{ "categories": ["Residential_Contractors", "Contractor_Registrations"], "statusFilter": "issued", "maxRecords": 0 }
Recipe 2: Full Minnesota DLI export in one shot (fastest path)
{ "useZip": true, "statusFilter": "all", "maxRecords": 0 }
Recipe 3: Active master plumbers (compliance / recruiting)
{ "categories": ["Plumbing"], "statusFilter": "issued", "maxRecords": 0 }
Then filter downstream: [r for r in items if (r.get("licenseType") or "").lower().startswith("master")]
Recipe 4: All active Mechanical Contractor Bond holders (HVAC firm list)
{ "categories": ["Mechanical_Contractor_Bond"], "statusFilter": "issued", "maxRecords": 0 }
Returns every business with a current mechanical / HVAC contractor bond filing — firm name, address, phone, and status.
Recipe 5: Residential contractors & remodelers (insurance underwriting)
{ "categories": ["Residential_Contractors"], "statusFilter": "issued", "maxRecords": 0 }
Recipe 6: Sample 50 records before committing to a full scrape
{ "categories": ["Electrical"], "statusFilter": "all", "maxRecords": 50 }
Recipe 7: Boiler operators + high-pressure pipefitters (industrial workforce)
{ "categories": ["Boiler", "High_Pressure_Piping"], "statusFilter": "issued", "maxRecords": 0 }
Integration Examples
Google Sheets
Set up an Apify schedule running this actor nightly at 02:00 Central, add the "Export to Google Sheets" integration, and receive a fresh MN DLI license CSV in your Sheet every morning ready for VLOOKUPs, pivot tables, and conditional-format dashboards.
Make.com / Zapier / n8n
Use the official Apify connector on any major automation platform. Trigger downstream workflows on new records (alert sales on net-new Twin Cities contractors), status changes (Issued → Suspended opens a compliance ticket), address changes (territory rebalancing), or new enforcement actions (Slack / Teams push).
Power BI / Tableau / Looker
Connect the Apify REST API as a data source and refresh on the actor's schedule. Useful MN DLI dashboards: active license count by category, new-issuance rate over time (a leading demand indicator), license expiration calendar (30/60/90-day), status mix (Issued vs Expired), and contractor density heat maps by city and ZIP across the Twin Cities and greater Minnesota.
Postgres / Snowflake / BigQuery / Redshift
Use the Apify webhook integration to POST run results directly to your warehouse ingestion endpoint after every scheduled run. The flat JSON schema maps 1:1 to a single normalized table (license_number as PRIMARY KEY, source_category for routing, dates as DATE, bond_amount as NUMERIC) — no JSON unnesting required.
Salesforce / HubSpot / Pipedrive CRM Enrichment
Trigger an Apify run nightly, then upsert against your Account records keyed on DLI license number. Status-change events can auto-create Tasks, open Cases, or trigger sequences. Pair with the responsibleIndividual field to populate Contact records for the qualifying person at firm-licensed accounts.
Direct Mail & Email Service Providers
Export the dataset as CSV and load into Lob, Click2Mail, or any direct-mail platform — DLI publishes the full business/practice mailing address (and a phone number on ~85–90% of records) that maps cleanly to direct-mail input requirements. Note: the DLI export does not publish email addresses, so this dataset is best suited to postal mail and phone outreach rather than email campaigns.
Major Minnesota Markets at a Glance
| Metro / Region | County | Population | Construction Relevance |
|---|---|---|---|
| Minneapolis | Hennepin | 430K (3.7M metro) | Largest construction market in MN; densest electrical, plumbing, HVAC base |
| Saint Paul | Ramsey | 311K | State capital; commercial and government construction |
| Rochester | Olmsted | 121K | Mayo Clinic expansion drives mechanical, electrical, piping demand |
| Duluth | Saint Louis | 87K | Port/industrial center; high-pressure piping, boiler work |
| Bloomington | Hennepin | 89K | Mall of America corridor; commercial GC + HVAC density |
| Plymouth | Hennepin | 80K | Affluent western suburb; high residential remodel + roofer volume |
| Edina | Hennepin | 53K | Tear-down + luxury remodel hotspot |
| Maple Grove | Hennepin | 71K | Fastest-growing NW suburb; new-construction roofer demand |
| Saint Cloud / Mankato | Stearns / Blue Earth | 69K / 45K | Central + south-central MN trades hubs |
| Eagan / Burnsville | Dakota | 68K / 64K | South-metro mechanical / HVAC concentration |
Cost & Performance
| Metric | Value |
|---|---|
| Engine | Direct CSV / ZIP download — no browser overhead |
Runtime (single category, default maxRecords:200) | 5–15 seconds |
| Runtime (full ZIP, unfiltered) | 30–90 seconds |
| Cost per run | Pay-per-event — typically a few cents per full export |
| Pricing model | Pay-per-event (transparent per-record pricing) |
| Data freshness | Live at run time — DLI exports refresh continuously |
| Auth required | None |
| Proxy required | None (Apify proxy supported but not needed) |
| Concurrency | Safe to run multiple parallel filtered configurations |
| Memory footprint | 256 MB default; up to 4096 MB available for the full ZIP scrape |
| Retries | 3 attempts per file with linear backoff (2 s, 4 s) |
| Failure mode | Graceful — partial results are written before non-zero exit |
Compliance, Privacy & Legal Notes
- Public data only — every field in this dataset is published by the Minnesota Department of Labor and Industry at
secure.doli.state.mn.usunder Minnesota Government Data Practices Act (Minn. Stat. Ch. 13) - No PHI / no patient data — these are professional license records, not health information; HIPAA does not apply
- No SSNs, DOBs, or financial account data — only license-related public information is published by DLI
- Address data is the business or practice address of record with DLI (in most cases business mailing address; for individual licensees it may be a home address where that is the address-of-record)
- Phone fields are published only where the licensee provided a number to DLI (~85–90% of records); the email field is part of the export schema but DLI currently leaves it empty, so it is almost always
null - The dataset must not be used for unlawful purposes including identity fraud, harassment, stalking, or any use that violates the Minnesota Government Data Practices Act, CAN-SPAM, TCPA, GDPR, CCPA, or other applicable laws
- Robots.txt / ToS — DLI publishes the CSV / ZIP exports for public download; this actor only consumes the documented bulk-export endpoints
- Compliance with direct-mail, telemarketing, and email-marketing law is the responsibility of the data consumer
Important: DLI license data is provided for licensing transparency. Anyone using this dataset for hiring, lending, insurance, or housing decisions should also consider FCRA-style permissible-purpose obligations and verify the most current status directly with DLI before taking adverse action.
Frequently Asked Questions
How fresh is the data?
DLI updates its bulk CSV and ZIP exports continuously as licenses are issued, renewed, expired, suspended, or revoked. Every run downloads the latest version live from DLI's server — typically no more than a few hours stale.
How many records will I get?
Counts shift constantly. Across all eight categories the full export is roughly 275,000 records — the Electrical file alone is ~110,000 rows — including a long tail of expired/inactive history when statusFilter: "all". Use issued to keep only active licenses. Set maxRecords: 0 for the full export, or a small positive number (or a bare run, which returns a ~100-record mixed sample) for a cheap sampler.
Does this scraper require login or API keys to DLI?
No. DLI publishes the CSV files and combined ZIP as public downloads with no authentication, no captcha, and no API key. You only need an Apify account.
Does this work for other states (Wisconsin, Iowa, North Dakota, South Dakota)?
Not this actor — DLI is Minnesota-specific. Haketa maintains separate actors for other state licensing boards. See the Related Actors section.
Can I use this for license verification at scale?
Yes. Many GCs and insurers run this nightly, diff against the previous day, and trigger alerts on status changes via Make.com / n8n / Slack / Teams.
Are licensee emails and phones included?
Phone: yes, on roughly 85–90% of records (where the licensee provided one to DLI). Email: effectively no — the DLI public export includes an Email_Address column but ships it empty across all categories, so the email field is almost always null. Treat this dataset as a postal-mail and phone source, not an email source. The email field remains in the schema in case DLI begins publishing it.
What's the difference between categories mode and useZip mode?
categories mode makes one HTTPS GET per selected category (faster for partial scrapes). useZip: true makes one GET for the combined archive containing all eight CSVs (faster for full exports). Output records are identical.
Why is the default maxRecords set to 200?
To keep first-time test runs cheap. Set maxRecords: 0 for production scrapes.
Does the actor deduplicate?
DLI's exports are already deduplicated by license number. If the same business holds licenses in multiple categories, each license appears as its own record.
What's responsibleIndividual?
For firm/business licenses, MN DLI requires a qualifying individual (the "responsible individual") who holds the personal credential the firm operates under. This field exposes that person's name when DLI publishes it.
Residential_Contractors vs Contractor_Registrations? Boiler vs High_Pressure_Piping?
Residential Contractors hold a true license (RBC or RR). Contractor Registrations cover smaller sub-threshold contractors using MN's registration pathway. Boiler licenses cover operators/inspectors of pressure vessels; High Pressure Piping licenses cover contractors/pipefitters installing high-pressure pipe systems (typically above 15 psi steam / 160 psi water).
Is residential or proxy access required?
No. DLI does not block, geo-restrict, or rate-limit public file downloads. Optional Apify proxy is supported via proxyConfiguration for orgs that require it.
Is there a historical snapshot version of the data?
Not directly — DLI publishes a point-in-time export. To build a history, schedule this actor daily/weekly and archive each dataset run.
Can I get federal court / litigation records on a contractor through this actor?
No — this actor covers Minnesota DLI licensing only. For federal court dockets, opinions, and party records (useful for construction-defect litigation and contractor due diligence), see the Federal Court Records Scraper (PACER / RECAP) and pair the two datasets.
Does this actor work with the Apify Free Plan?
Yes — full functionality on the free tier.
Can I run this on a schedule automatically?
Yes — Apify's Scheduler triggers any cron expression. Combine with webhooks for a fully automated nightly compliance pipeline.
What formats can I export the data in?
JSON, CSV, Excel (XLSX), HTML, XML, RSS — directly from the Apify dataset view. The API also supports JSON Lines.
Does this dataset include workers comp claims or injury data?
No. DLI's bulk regulant exports cover licensing and registration only. Workers comp claim data is handled separately by DLI's Workers Compensation Division and is not part of these public CSV files.
Related Apify Actors by Haketa
If you need licensing data from other US states or related construction/government datasets, these sibling actors all live on the same publisher account:
- Arizona ROC Contractor License Scraper — Arizona Registrar of Contractors licensee database
- Washington L&I Contractor License Scraper — WA Department of Labor and Industries contractor data
- Virginia DPOR Professional License Scraper — Virginia Department of Professional and Occupational Regulation
- Colorado Professional License Scraper — Colorado DORA professional licenses
- California DCA Professional License Scraper — California Department of Consumer Affairs
- Ohio eLicense Scraper — Ohio eLicense central system
- Illinois License Scraper (IDFPR) — Illinois Department of Financial and Professional Regulation, 1.2M+ records
- NC Licensing Board for General Contractors Scraper — North Carolina general contractor licenses
- Texas Pharmacy License Scraper — TSBP — Texas State Board of Pharmacy daily data
- TTB Alcohol Permittee Scraper — Federal TTB alcohol permittees
- Federal Court Records Scraper (PACER / RECAP) — federal court dockets, opinions, and party records for construction litigation and due diligence
- BBB Business Scraper — Better Business Bureau profiles for credential cross-reference
Comparison vs. Alternatives
| Approach | Setup time | Data freshness | Cost (full export) | Schema normalization | Compliance audit log |
|---|---|---|---|---|---|
| This actor | < 1 minute | Live | A few cents per run | Built-in (100+ header aliases) | Automatic (scrapedAt) |
| Manual CSV / ZIP download | 5–10 min/day | Live | Free | None | None |
| Custom Python / Node script | 4–8 hours dev | Live | Free + infra | DIY (and re-DIY when DLI drifts) | DIY |
| Paid license-verification API | Hours of vendor setup | Real-time per-lookup | $100–500+/mo, per-lookup | Yes | Yes |
| DLI public records request | Days–weeks | Stale by the time you get it | Variable | None | None |
Why Pay-Per-Event Pricing?
Most data scrapers either charge a flat monthly subscription (you pay even if you don't use it) or per-Compute-Unit (unpredictable). This actor uses pay-per-event pricing, which means:
- You only pay when the actor runs
- Charges scale with how many records you actually save
- Transparent line-item billing inside the Apify Console
- No monthly minimums and no commitment
- Free to evaluate — sample 50 records with
maxRecords: 50for pennies before scaling to the full export
Changelog
| Version | Date | Notes |
|---|---|---|
| 1.0.0 | 2026-05 | Initial public release — 8 DLI categories supported, ZIP and per-category modes, 100+ header aliases, malformed-row tolerance, HTML-response guard, status filter, batched dataset writes, graceful partial-save on failure |
Keywords
Minnesota DLI license lookup · MN contractor verification · Minneapolis license data · Minnesota plumbing electrician license · DLI license search · Minnesota builder license lookup · Minnesota Department of Labor and Industry scraper · MN DLI scraper · Minnesota residential building contractor data · Minnesota residential remodeler license · Minnesota electrical contractor license · Minnesota master plumber lookup · Minnesota journeyworker plumber data · Minnesota high pressure piping license · Minnesota boiler operator license · Minnesota elevator constructor license · Minnesota contractor registration data · Mechanical contractor bond Minnesota · MN HVAC mechanical contractor list · Minneapolis contractor database · Saint Paul contractor lookup · Rochester MN contractor data · Duluth contractor license · Bloomington MN contractor list · Plymouth Minnesota contractor verification · Edina MN remodeler license · Maple Grove contractor license · Hennepin County contractor database · Ramsey County license data · Olmsted County contractor list · MN contractor lead generation · Minnesota construction compliance API · DLI CSV download · MN tradesperson workforce data · Apify Minnesota DLI actor · contractor license verification Minnesota · Minneapolis HVAC contractor lookup · Saint Paul electrician database · Minnesota contractor enforcement action data · Twin Cities contractor lead list · Minnesota construction permit cross-reference · MN contractor credentialing automation
Support
- Bug reports: Use the Issues tab on the Apify Store page for
haketa/minnesota-dli-license-scraper - Feature requests: Same place, please describe your use case (Wisconsin / Iowa / North Dakota / South Dakota equivalents are popular asks)
- Direct contact: Through the Apify developer profile
If this actor saves you time, a 5-star rating on the Apify Store helps other Minnesota construction, compliance, and insurance teams discover it. Thank you!
📅 Changelog & Maintenance
Last updated: 2026-07-02 — Actor verified and maintained. Data pipeline tested for quality, structure and freshness; selectors/endpoints confirmed against the live site.