Minnesota DLI Professional License Scraper avatar

Minnesota DLI Professional License Scraper

Pricing

from $1.20 / 1,000 results

Go to Apify Store
Minnesota DLI Professional License Scraper

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

Haketa

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

0

Monthly active users

7 days ago

Last modified

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.

Apify Actor


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 Electrical file 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 csv parse and silently zeroes out the entire category
  • Header casing can drift between exports (Lic_Number vs Lic_No vs License_Number) and breaks string-keyed parsers
  • The Bus_Pers field is a single-letter code (B/P) that must route Name into either businessName or individualName
  • Addr1 + Addr2 come as separate columns and must be merged; ZIPs ship with a -0000 suffix that needs stripping
  • Date columns use MM/DD/YYYY and must be normalized to ISO YYYY-MM-DD for 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 requests setups

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

  1. Click "Try for free" on the Apify Store page for haketa/minnesota-dli-license-scraper
  2. Pick specific categories (prefilled with Mechanical_Contractor_Bond and High_Pressure_Piping) or tick Include All Categories to grab everything in one shot
  3. Hit Start — full export ready in under two minutes
  4. 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 ApifyClient
client = 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 FileURL PatternCoverage
MNDLILicRegCertExport_Electrical.csvhttps://secure.doli.state.mn.us/ccld/data/MNDLILicRegCertExport_Electrical.csvElectrical contractors, masters, journeyworkers, installers, PLTs
MNDLILicRegCertExport_Plumbing.csvsame path with _PlumbingMaster/journeyworker/restricted plumbers, plumbing contractors
MNDLILicRegCertExport_Residential_Contractors.csv..._Residential_ContractorsRBC, RR licenses
MNDLILicRegCertExport_Contractor_Registrations.csv..._Contractor_RegistrationsRegistered (non-licensed) construction contractors
MNDLILicRegCertExport_Mechanical_Contractor_Bond.csv..._Mechanical_Contractor_BondMechanical/HVAC contractor bond filings
MNDLILicRegCertExport_Boiler.csv..._BoilerBoiler operators, inspectors, special licenses
MNDLILicRegCertExport_High_Pressure_Piping.csv..._High_Pressure_PipingHigh-pressure pipefitters, pipefitting contractors
MNDLILicRegCertExport_Elevator.csv..._ElevatorElevator constructors, limited mechanics, contractors
MNDLILicRegCertExport.ziphttps://secure.doli.state.mn.us/ccld/data/MNDLILicRegCertExport.zipCombined archive of all eight files

Engineering details

  • Direct HTTPS GET via got-scraping with a desktop Chrome User-Agent — no headless browser, no API key
  • Two ingestion modes — individual category CSVs (categories) or combined MNDLILicRegCertExport.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_Number all map to licenseNumber)
  • Malformed-row tolerance — skip_records_with_error drops only the rare rows with unescaped embedded quotes (e.g. a business name like PlayNo"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; merged name preserved as convenience
  • Address merging — Addr1 + Addr2 joined with a comma; ZIP normalization strips trailing -0000; date normalization coerces MM/DD/YYYY, YYYY/MM/DD, YYYY-MM-DD to ISO
  • Status filter — issued keeps active licenses only; all keeps 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.
  • maxRecords caps 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 positive maxRecords automatically 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

ParameterTypeDefaultDescription
categoriesarray<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.
useZipbooleanfalseWhen true, downloads the single combined MNDLILicRegCertExport.zip archive and extracts all eight CSVs in one round trip. Overrides categories. Recommended for full-export jobs.
statusFilterstringallissued returns only currently active/issued licenses. all returns every record including expired, revoked, suspended, surrendered, and retired.
maxRecordsinteger0 (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.
requestDelayinteger200Milliseconds to sleep between category downloads in individual-CSV mode. Has no effect in ZIP mode.
proxyConfigurationobject{ 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

FieldTypeAlways PresentDescription
licenseNumberstring~100%DLI-issued license, registration, or certificate number — typically a 2-letter category prefix plus digits
licenseTypestring~100%License type as reported by DLI (e.g., Electrical Contractor, Master Plumber)
licenseSubtypestring~100%Specialty or sub-class (e.g., Class A, Restricted, Limited)
sourceCategorystringyesOne of the eight file categories — Electrical, Plumbing, etc. — useful for downstream routing
entityTypestringyesNormalized Business or Personal (derived from DLI's single-letter B/P code)
businessNamestringwhen entityType=BusinessFirm / company name
individualNamestringwhen entityType=PersonalPerson name
namestring~100%Convenience merged name field (business name or individual name)
dbastring~5% (data-dependent)Doing-Business-As name — only where a business operates under a different name
responsibleIndividualstringoptionalQualifying person on file (populated only on category files that publish this column)
statusstring~100%Raw status (Issued, Expired, Revoked, Suspended, Surrendered, etc.)
issueDatestring~100%Original issue date in YYYY-MM-DD format
expirationDatestring~100%Current expiration date in YYYY-MM-DD format
addressstring~100%Full mailing address (Addr1 + Addr2 merged)
citystring~100%City
statestring~100%Two-letter US state abbreviation
zipCodestring~100%ZIP code (-0000 suffix stripped)
countystringoptionalMinnesota county name (populated only on category files that publish this column)
phonestring~85–90% (data-dependent)Contact phone number — present where the licensee provided one
emailstringpresent when availableContact 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
bondAmountstringMechanical bond filings (when published)Surety bond face value — populated only if DLI exposes a bond-amount column
bondCompanystringMechanical bond filings (when published)Issuing surety/bond company — populated only if DLI exposes a surety column
enforcementActionstring~100%Enforcement-action flag (0 = none on file)
renewalInProgressstringoptionalIndicator that a renewal is currently being processed
scrapedAtstringyesISO-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, email, responsibleIndividual, bondAmount, and bondCompany are null above 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.

StatusPractice/Operate?Meaning
IssuedYesLicense is currently active and in good standing
ExpiredNoFailed to renew by the deadline; may be reinstated
RevokedNoTerminated by Board action
SuspendedNoTemporarily barred from practice
SurrenderedNoVoluntarily relinquished (often pre-disciplinary)
RetiredNoRetired credential — no longer practicing
CancelledNoAdministratively cancelled
PendingConditionalApplication in progress

Tip: When statusFilter is set to issued, the actor performs a case-insensitive exact match on Issued. All other statuses are filtered out and tallied in the totalSkipped log 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 Issued to Expired and trigger a procurement alert
  • Capture enforcementAction flags so a sub with a fresh administrative penalty surfaces in the next compliance review
  • Build audit-ready logs with the timestamped scrapedAt field 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 Issued record

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 Issued to Suspended or Revoked
  • 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 / RegionCountyPopulationConstruction Relevance
MinneapolisHennepin430K (3.7M metro)Largest construction market in MN; densest electrical, plumbing, HVAC base
Saint PaulRamsey311KState capital; commercial and government construction
RochesterOlmsted121KMayo Clinic expansion drives mechanical, electrical, piping demand
DuluthSaint Louis87KPort/industrial center; high-pressure piping, boiler work
BloomingtonHennepin89KMall of America corridor; commercial GC + HVAC density
PlymouthHennepin80KAffluent western suburb; high residential remodel + roofer volume
EdinaHennepin53KTear-down + luxury remodel hotspot
Maple GroveHennepin71KFastest-growing NW suburb; new-construction roofer demand
Saint Cloud / MankatoStearns / Blue Earth69K / 45KCentral + south-central MN trades hubs
Eagan / BurnsvilleDakota68K / 64KSouth-metro mechanical / HVAC concentration

Cost & Performance

MetricValue
EngineDirect CSV / ZIP download — no browser overhead
Runtime (single category, default maxRecords:200)5–15 seconds
Runtime (full ZIP, unfiltered)30–90 seconds
Cost per runPay-per-event — typically a few cents per full export
Pricing modelPay-per-event (transparent per-record pricing)
Data freshnessLive at run time — DLI exports refresh continuously
Auth requiredNone
Proxy requiredNone (Apify proxy supported but not needed)
ConcurrencySafe to run multiple parallel filtered configurations
Memory footprint256 MB default; up to 4096 MB available for the full ZIP scrape
Retries3 attempts per file with linear backoff (2 s, 4 s)
Failure modeGraceful — partial results are written before non-zero exit

  • Public data only — every field in this dataset is published by the Minnesota Department of Labor and Industry at secure.doli.state.mn.us under 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.


If you need licensing data from other US states or related construction/government datasets, these sibling actors all live on the same publisher account:


Comparison vs. Alternatives

ApproachSetup timeData freshnessCost (full export)Schema normalizationCompliance audit log
This actor< 1 minuteLiveA few cents per runBuilt-in (100+ header aliases)Automatic (scrapedAt)
Manual CSV / ZIP download5–10 min/dayLiveFreeNoneNone
Custom Python / Node script4–8 hours devLiveFree + infraDIY (and re-DIY when DLI drifts)DIY
Paid license-verification APIHours of vendor setupReal-time per-lookup$100–500+/mo, per-lookupYesYes
DLI public records requestDays–weeksStale by the time you get itVariableNoneNone

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: 50 for pennies before scaling to the full export

Changelog

VersionDateNotes
1.0.02026-05Initial 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.