How-to
How to run New Mexico Contractor License Lookup & Verify (NM CID)
Step-by-step: the exact input, what each field does, and the real rows that come back from NM CID.
You have a New Mexico contractor to check — a sub you’re about to put on a job, a vendor coming through onboarding, a name on an insurance application. The state’s public registry has the answer, and it is free. The catch is the shape of it: every single search on the NM CID portal makes you read a smudged word out of an image and type it in before it will show you anything. One contractor, that’s ten seconds of annoyance. A list of forty, and you are typing forty CAPTCHAs and copy-pasting forty rows into a spreadsheet by hand, because the portal has no export button. This actor runs those searches for you and hands back the same records as a table.
What you’ll need
Nothing but an Apify account. The actor talks to NM CID directly.
No proxy, no API key, no browser. The one thing this actor needs that most don’t is a CAPTCHA solve — the portal’s search form is gated by a distorted-text image (see it further down this page), and the actor pays an automated solving service to read it on every search. You don’t supply anything for that and you don’t hold an account with the solver — the cost is folded into the actor’s per-search fee, which is why this actor prices differently from the rest of the suite. More on that under “What it costs”.
Step 1 — Open the actor
Open New Mexico Contractor License Lookup & Verify (NM CID) on the Apify Store and press Try for free. The actor’s own page on this site lists its live health and pricing.
Step 2 — Fill in the input
| Field | Type | Required | What it does |
|---|---|---|---|
businessName | string | no | Search by contractor business name (partial matches allowed, e.g. ‘Smith’). Combine with other fields to narrow results. |
licenseNumber | string | no | Exact NM CID contractor license number. When set, returns that specific license. |
city | string | no | Filter results to a business city (e.g. ‘Albuquerque’). |
zipCode | string | no | Filter results to a business ZIP code. |
maxResults | integer | no | Cap on the number of license matches returned in a single run. The portal returns 20 per page and this actor reads only the first page, so this is capped at 20. (default 20) |
Every field is optional, but the actor needs at least one of licenseNumber, businessName,
city, or zipCode to have something to search on. Leave them all blank and the run still
succeeds: you get one free row saying which fields it wanted, and nothing is charged — no CAPTCHA
is solved, so there is nothing to pay for.
Two fields decide almost everything you get back:
licenseNumber is the precise one. Set it and you are asking the registry a closed question:
does this license exist, and what is its status? You get that one license back.
businessName is the one people get wrong. It’s a partial match, so Smith is enough to pull
back ADAMS & SMITH, INC. — but the portal has rules about what it will accept. Its own form says
you must type at least three characters, and at least the first word of the name including any
punctuation in it, so a company filed as 3J'S needs the apostrophe. Type the name as the state
filed it, not as people say it.
city and zipCode narrow, they don’t broaden. maxResults cannot exceed 20 — that isn’t a
throttle we chose, it’s the portal’s page size, and this actor reads the first page only.
A working input:
{
"businessName": "Smith",
"maxResults": 20
}
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 run does one search: it loads the portal, sends the CAPTCHA image off to be read, submits the form with the answer, and parses the results page. That means the CAPTCHA solve sits in the middle of every run, and it is the slowest part — a solving service is a queue, not a function call, so this actor is not instant the way a plain JSON-API actor is.
A healthy run returns between 1 and 20 rows, deduplicated by license number. Zero rows is a normal
outcome, not a broken actor: it means the registry has nothing matching those filters. Before you
assume the actor is at fault, check the obvious things — a businessName that doesn’t start the way
the state filed it, a city combined with a zipCode that don’t overlap, a license number with a
typo. A genuinely broken run looks different: it fails loudly with an error about the CAPTCHA being
rejected or the page layout having changed, and charges you nothing for the search.
Step 4 — Read the output
Each run returns a labelled table, not raw JSON:
Every row looks like this — a real row from a real run:
{
"state": "NM",
"license_number": "396966",
"business_name": "ADAMS & SMITH, INC.",
"person_name": null,
"license_type": "Contractor",
"status": "Active",
"issue_date": null,
"expiration_date": "12/31/2027",
"city": "LINDON",
"county": null,
"address": "1380 W CENTER ST",
"zip_code": "84042",
"phone": null,
"bond_amount": null,
"bond_company": null,
"insurance_company": null,
"matched_by": "business_name",
"source_url": "https://public.psiexams.com/search.jsp"
}
| Field | Example value | Meaning |
|---|---|---|
state | NM | Always ‘NM’ for this actor |
license_number | 396966 | NM CID contractor license number |
status | Active | License status (e.g. Active, Cancelled) |
license_type | Contractor | Always ‘Contractor’ (NM CID’s contractor company category) |
business_name | ADAMS & SMITH, INC. | Licensed business/company name |
address | 1380 W CENTER ST | Principal place of business street address |
city | LINDON | Business city |
zip_code | 84042 | Business ZIP code |
Most people came for two of them: status and expiration_date. Together they answer the only
question a verification really asks — is this license good right now? license_number is the
column you key on afterwards, because it’s the one stable identifier; business names get re-filed
and re-spelled, license numbers don’t.
Watch the nulls. The sample row above is a real one, and half its fields are null — county,
phone, bond_amount, bond_company, insurance_company, issue_date, person_name. That isn’t
missing data on our side. Those fields simply are not on the results page the portal returns; they
live behind a per-record detail view that is triggered by JavaScript rather than a URL, so there is
nothing to link to and nothing to fetch. If your workflow needs bond or insurance figures, this
actor will not give them to you, and no amount of retrying will change that. source_url points at
the search portal rather than the individual record for the same reason.
One more thing to expect: the registry stores addresses as filed, which is not always in-state. The
sample row’s city is LINDON with a Utah ZIP — an out-of-state company holding a New Mexico
license. That’s the source telling the truth, not a parsing bug.
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
This actor charges twice, and it’s worth understanding why, because it’s the only one in the contractor suite shaped this way.
- $0.02 per search. Charged once per run, including when the search returns nothing.
- $0.01 per license match. Charged per row actually returned.
A run that pulls a full page of 20 licenses costs $0.02 + 20 × $0.01 = $0.22. A run that verifies one license number costs $0.03. A run that finds nothing costs $0.02.
That per-search fee exists because every search on this portal has a real marginal cost attached to it: the CAPTCHA has to be read by a paid solving service before the form will submit, and it has to be paid for whether or not the registry has a match waiting on the other side. Charging it up front is the honest version — the alternative is inflating the per-row price so that people running broad searches subsidise people running empty ones. Apify’s platform compute is billed separately, on whatever plan you’re on.
Where the data comes from
This reads NM CID directly. The target is HTML form POST. Every night a canary runs this actor against that live source and diffs the result against a frozen fixture — what “verified” means.
The New Mexico contractor registry isn’t hosted by the state at all — it lives on PSI Exams, the
testing vendor that runs the state’s licensing e-services, at public.psiexams.com. It’s an old
Java web app: a plain HTML form POST with no API behind it, no per-record URLs, and a distorted-text
image CAPTCHA (Java’s SimpleCaptcha) sitting on the search submission. That last part is what makes
it awkward. It’s not a Cloudflare or PerimeterX JS-challenge wall — those we can’t beat and we don’t
claim to — it’s a picture of a word that a human is expected to read. Clearing it costs money on
every search, which is precisely why this actor prices the way it does. The tier and the mechanism
are written down on hard targets, next to the nightly pass rate that shows we
actually get through.
See it used
- Verify a New Mexico Contractor by License Number — the compliance check: one license in, status and expiry out.
- Find Every New Mexico Contractor License by Name — when you have a company name and need every license filed under it.
- List New Mexico Contractors in a City — a page of licensed contractors in one city, as a table.
If the contractor turns out not to be a New Mexico one, or you don’t yet know which state they’re licensed in, reach for Multi-State Contractor & Trade License Lookup instead — it checks the same contractor across several states in one run. And if you’re building against more than one CAPTCHA-walled registry, South Carolina Contractor License Lookup & Verify (SC LLR) is the other one in the suite that has to solve its way in — a reCAPTCHA there rather than an image, but the same two-event pricing for the same reason.
Last updated 2026-07-13