Use case
Give an AI agent the ability to verify a contractor licence
Wiring a 17-state licence check into an LLM tool-calling loop - and the three verdicts your prompt has to handle before you ship it.
Ask any general-purpose AI agent whether a contractor is licensed and you will get one of two bad answers: a confident hallucination, or “I can’t check that.” Neither is wrong by accident. There is no public API for US contractor licensing. Every state runs its own board, and the boards are web portals built for humans — California’s is ASP.NET WebForms, South Carolina’s puts a reCAPTCHA on the search itself, Virginia publishes a bulk roster with no search at all.
So a vendor-onboarding agent, a procurement assistant or a CRM intake flow simply cannot close that loop today. It collects the contractor’s details and then hands off to a person.
What the tool call looks like
Contractor License Verify is shaped for exactly one question. One state, one contractor, one flat object:
{ "state": "CA", "licenseNumber": "22726" }
{
"verdict": "active",
"verdict_reason": "status 'This license is current and active.', expires 2027-10-31",
"verdict_basis": "status+expiry_date",
"entity_name": "RANSOME COMPANY",
"status_raw": "This license is current and active.",
"expiry_date": "10/31/2027",
"source_portal": "CSLB",
"checked_at": "2026-08-06T19:20:11+00:00"
}
Flat on purpose — an agent should never have to walk a nested object to find the answer. And every
row carries source_portal and checked_at, so the agent can cite where the answer came from and
when, which is the difference between a useful assistant and a confident one.
The three verdicts your prompt must handle
This is the part to get right before you ship. Five verdicts are findings about the licence:
active, expired, revoked, suspended, inactive. Three are statements about the check,
and an agent that treats them as findings will say something false about a real business.
not_found is not “unlicensed.” It means the registry returned no match for what was searched.
A typo, a DBA that differs from the licensed entity name, a licence held under a parent company —
all produce not_found. An agent that reports “this contractor is not licensed” on the back of it
has published a false and damaging claim about a real company. Branch it to “I couldn’t find a
matching licence — the name may differ from the registered entity. Worth checking manually.”
unverifiable_state_not_covered is not silence. Seventeen states are covered. The other
thirty-three return this, explicitly, rather than nothing — because an empty response gets
summarised as “no problems found.”
check_failed is never a pass. A board that is down, a name matching two licences, a record
missing a field that board normally publishes. On 2026-08-06, Florida’s DBPR went into scheduled
maintenance and served a normal-looking page saying so; before that was caught, it parsed to zero
results and would have reported every Florida contractor as not_found. It now returns
check_failed. Fail closed, always.
And one distinction the boards force
inactive exists because “Lapsed”, “Voluntary Surrender”, “Archived” and “Voluntary Termination”
are all real strings from real boards, and none of them is a disciplinary action. revoked and
suspended are. An agent repeats whichever word you hand it, so those are kept apart at the source
rather than left to the model’s judgement. If your prompt paraphrases, make sure it does not
collapse them.
What it can and cannot stand on
Boards do not publish the same fields, so verdict_basis reports what each answer rests on:
| Board publishes | States | Verdict rests on |
|---|---|---|
| Status and expiry | AR, CA, CT, FL, MA, MI, MN, NM, NV, OR, TN, WA | status+expiry_date |
| Expiry only (no status field) | AL, TX, VA | expiry_date — a revocation would not be seen |
| Status only (no expiry date) | NC, SC | status — expiry cannot be checked |
Virginia additionally cannot be searched by licence number at all; a number-only request there
returns check_failed telling you to supply the name.
Latency, because agents time out
Measured 2026-08-06, one lookup per state: median 13.3s, fastest 10.6s, slowest 30.8s for
South Carolina, whose board costs a CAPTCHA solve per check. New Mexico is 19.6s for the same
reason. Set your tool timeout above 35 seconds. If you are verifying a list, use the
licenseNumbers array — one run for the batch, billed identically, and far faster than a run each.
Cost
$0.004 per verdict resolved, plus the underlying state lookup — about $0.008 per check in the
open-data states and $0.034 in New Mexico and South Carolina. Verdicts that describe the check
rather than the contractor (not_found, unverifiable_state_not_covered, check_failed) are
free.
Start with the how-to guide for the full input and output reference, or call Contractor License Verify on the Apify Store. Watching a whole roster instead of answering one question? Contractor License Expiration Monitor re-checks a watchlist on a schedule and delivers alerts to Notion.
Last updated 2026-08-06