← Guides

How-to

How to run Massachusetts Construction License Lookup & Verify (CSL)

Step-by-step: the exact input, what each field does, and the rows that come back from MA OPSI (MyLicense).

The honest starting point: Massachusetts publishes this data for free. The Office of Public Safety and Inspections runs a public MyLicense verification portal, and if you have one Construction Supervisor to check before you hire him, go and check him there — it’s the same registry this actor reads, and it costs nothing. This page is for the job that isn’t one lookup. You’re onboarding a list of subs and each one needs a CSL number, type, status and expiration in a spreadsheet. You re-verify every active supervisor each quarter, because a license that read Active in January can read Expired by April. Or you have a last name and no license number, and you need every CSL that name is attached to. By hand that’s a form fill, a results grid, a click into each detail page, and a dozen fields copy-pasted per licensee — repeated for every name, and thrown away the moment the session ends. This actor does the same searching and returns structured rows instead.

What you’ll need

Nothing but an Apify account. The actor talks to MA OPSI (MyLicense) directly.

No API key, no CAPTCHA-solving service, no proxy subscription, no browser. The portal is public and plain HTTP — recon confirmed there’s no CAPTCHA anywhere in the search form or its submit — so the actor fetches it with a normal HTTP client, and the only bill is the actor’s per-result fee plus Apify compute. There’s no third-party cost hiding behind it.

Step 1 — Open the actor

Open Massachusetts Construction License Lookup & Verify (CSL) on the Apify Store and press Try for free. The actor’s own page on this site lists its live health and pricing.

The Massachusetts Construction License Lookup & Verify (CSL) listing on the Apify Store
The listing on the Apify Store. Pricing and the nightly health record are both public.

Step 2 — Fill in the input

The input form for Massachusetts Construction License Lookup & Verify (CSL) in the Apify Console
The input form, as it appears in the Apify Console.
FieldTypeRequiredWhat it does
querystringyesA Massachusetts Construction Supervisor License number (e.g. ‘CS-093362’ or just the digits) OR a last name. A license number is looked up directly; anything else is searched…
licenseTypestringnoRestrict the search to one Construction Supervisor License subtype. Default searches all three. (default “)
statusFilterstringnoExact license-status text to keep, e.g. ‘Active’, ‘Expired’, ‘Null and Void’, ‘Suspended’, ‘Revoked’. Default: no filter (all statuses).
includeDetailsbooleannoWhen on (default), each match is enriched with its full Details.aspx record — address, city, ZIP, and issue/expiration dates. Turn off for a faster, lighter result list (name,… (default True)
maxResultsintegernoCap on the number of matches returned (and enriched) in a single run. The portal itself caps each individual search at ~40 rows per page — narrow the query (name, license type,… (default 50)

query is the only required field, and it silently picks one of two searches for you. If what you type looks like a CSL number — CS-093362, CS093362, or just the digits — the actor runs a license-number search and you get that one license back. Anything else is treated as a last name and goes into the portal’s last-name field. That’s the part first-timers get wrong: this is a surname search, not a free-text company search. Smith works; Smith Construction LLC will not find anything, because the registry is indexed on the individual supervisor’s name, not on a business. The matched_by column in each row tells you which of the two searches produced it.

licenseType is the field worth thinking about second. Leave it empty (the default) and the actor searches all three CSL subtypes in turn — Construction Supervisor, Construction Supervisor 1 & 2 Family, and Construction Supervisor Specialty — and unions the results, deduped by license number. That’s the thorough setting and it’s three searches’ worth of work. Setting it to a single subtype is how you keep a common surname under the portal’s own per-search page cap of roughly 40 rows; statusFilter (an exact status string like Active) narrows the same way. includeDetails is on by default and adds one extra fetch per match to pull in the address, city, ZIP and the issue and expiration dates; turn it off and you get a faster, thinner list of name, license number, type and status only. maxResults (default 50) bounds the whole thing.

A working input:

{
  "query": "Smith",
  "licenseType": "",
  "includeDetails": true,
  "maxResults": 40
}

Step 3 — Run it

Press Start. Rows are pushed to the dataset as they’re found, so the run log fills in as it works.

A healthy run is short. A license-number lookup returns a single row. A last-name search returns as many CSL holders as the registry has under that surname, capped by maxResults — a distinctive name might be two or three rows, a common one will run into the portal’s ~40-row page limit. The work behind the run is one search per license subtype searched, plus one detail fetch per match when includeDetails is on, so an all-subtypes search on a common surname with details on is the slowest shape and a single-subtype number lookup is the fastest.

An empty run is almost never a broken actor. A license number that returns nothing is a number the registry doesn’t hold — check the digits, and remember the CS- prefix is optional but the number has to be a real CSL. A surname that returns nothing usually means you gave it something other than a surname (a company, a first name, a full name with initials) or a statusFilter string that doesn’t exactly match the registry’s own wording. Widen it: clear licenseType and statusFilter first, and search the bare last name on its own.

Step 4 — Read the output

Each run returns a labelled table, not raw JSON:

The output fields returned by Massachusetts Construction License Lookup & Verify (CSL)
The output view — every field the actor returns, named and typed.

Every row looks like this — a real row from a real run:

{
  "state": "MA",
  "license_number": "CS-093362",
  "person_name": "CHARLES JOHNSON IV",
  "business_name": null,
  "license_type": "Construction Supervisor",
  "status": "Null and Void",
  "issue_date": "2/9/2010",
  "expiration_date": "2/9/2014",
  "city": "Salisbury",
  "address": "17 LENA MAES WAY",
  "zip_code": "01952",
  "matched_by": "license_number",
  "source_url": "https://madpl.mylicense.com/Verification/Details.aspx?result=e42cc6bc-9d25-4fa1-a73b-656980e54be9"
}
FieldExample valueMeaning
stateMAAlways ‘MA’ for this actor
license_numberCS-093362MA Construction Supervisor License number (e.g. CS-093362)
person_nameCHARLES JOHNSON IVIndividual licensee full name
business_nameDoing-business-as name, when on file (detail)
license_typeConstruction SupervisorConstruction Supervisor, 1 & 2 Family, or Specialty
statusNull and VoidLicense status (e.g. Active, Expired, Null and Void, Suspended)
issue_date2/9/2010Original licensure date (detail)
expiration_date2/9/2014License expiration date (detail)

Most people came for status and expiration_date, and both need reading with a little care. status is the registry’s own wording, not a tidy enum: alongside Active and Expired you will see values like Null and Void, and the sample row above is exactly that — a real CSL that lapsed in 2014. If you’re going to branch on the field in code, match on the strings the registry actually emits rather than assuming a two-state Active/Expired world, and treat anything you don’t recognise as needing a human look. Dates come through as the portal prints them — 2/9/2010, unpadded month/day/year — so parse them, don’t sort them as text.

The rest of the caveats are about what’s present. business_name, address, city, zip_code, issue_date and expiration_date are detail fields: they only arrive when includeDetails is on, and business_name is null for the many supervisors with no DBA on file. The record schema is shared across the license-lookup suite, so a few columns (phone, county, bond, insurance) exist for consistency but are always null here — this registry simply doesn’t publish them. And one thing that’s genuinely peculiar to this portal: source_url points at a Details.aspx?result=<guid> page, and that guid is bound to the search session that produced it. It’s there as provenance, but don’t expect to paste it into a browser next week and land on the record. If you need a permanent citation for an auditor, cite the license number and re-run the lookup.

Step 5 — Export it

Open the Dataset tab and export to CSV, JSON, or Excel — or pull the same rows from the API, which is what you want if this is going to run on a schedule.

What it costs

$0.004 per license record returned. A search that finds nothing costs nothing — you are never charged for an empty result. Verifying one supervisor by CSL number is a single record, so four tenths of a cent. Running a 150-name onboarding list through it is 150 records, about $0.60, plus whatever extra rows the surname searches turn up. Apify’s platform compute is billed separately, per your plan.

Say the comparison plainly: against the state’s free portal this is not cheaper, it’s zero-effort and schedulable. One lookup, once? Use the free portal.

Where the data comes from

This reads MA OPSI (MyLicense) directly. The target is MyLicense (ASP.NET WebForms) - __VIEWSTATE / __EVENTVALIDATION harvested from the search page and replayed; results paged by __doPostBack. Every night a canary runs this actor against that live source and diffs the result against a frozen fixture — what “verified” means.

MyLicense is an ASP.NET WebForms application, and that is the whole reason this is more annoying than “fetch a URL”. There is no address you can request with a name in it. The search only works if you first load the form, harvest its hidden __VIEWSTATE and __EVENTVALIDATION tokens, and POST them back with your query — and those tokens change on every page load. Paging through the results grid isn’t a link either; it’s a __doPostBack that replays the tokens again. The sharpest edge is statefulness: the detail page for a match is addressed by a guid that is only valid inside the session’s current result set, so the moment you page forward, the guids from the previous page go dead. The actor therefore enriches each page of results before it moves on, which is why the detail fetches are interleaved with the paging rather than batched at the end. None of this is an anti-bot wall — there’s no CAPTCHA and nothing is trying to stop you — it’s just a portal built to be clicked by a person, one search at a time.

One more boundary worth knowing: this is the Construction Supervisor License (CSL) only. Home Improvement Contractor (HIC) registration lives on a completely separate Massachusetts system and is not covered here.

Verify a Massachusetts CSL by License Number checks a single CSL number before a sub goes on site.

If you don’t know which state a contractor is licensed in, or you want the same person checked in several states at once, reach for Multi-State Contractor & Trade License Lookup instead; come back here when you know the answer is Massachusetts and you want the full CSL detail record.

Last updated 2026-07-13