StateLicense blog

Contractor License API for AI Agents: an MCP Guide

A contractor license API for AI agents returns status, bond, and insurance as structured data. How agents call it via MCP, with tool schemas and code.

By StateLicensePublished

A contractor license API for AI agents turns fifty state board websites into one function call. Instead of teaching an agent to scrape board portals, you give it a single tool, verify_contractor_license, that takes a license number and a state and returns a normalized record: status, classifications, bond, insurance, and disciplinary flags. This guide explains how agents call a contractor license API through the Model Context Protocol (MCP), what the tool schema should look like, and what breaks when agents hit state boards directly.

Why would an AI agent verify a contractor license?

Three workflows already push license checks into agents.

Marketplace onboarding. A Thumbtack-style marketplace signs up hundreds of pros a week. An onboarding agent collects the license number, verifies it, and flags mismatches between the business name on the license and the business name on the application, before the pro ever appears in search results. See our contractor onboarding verification checklist for the full set of checks that surround the license lookup.

Construction lending. A disbursement agent re-verifies the general contractor and key subs before each draw. A license that was active at closing can be suspended by the third draw, and funding work performed under a lapsed license creates legal exposure.

Procurement and compliance. A general contractor's bidding agent vets every subcontractor before awarding work: correct classification for the scope, active bond on file, no open disciplinary actions. One bad sub can stall a project for months.

In all three cases the agent needs the same thing: a structured yes or no, with evidence, returned fast enough to fit inside a multi-step run.

How do agents call a license verification API through MCP?

MCP, the Model Context Protocol, is an open standard for connecting AI assistants to external data. An MCP server exposes tools (functions the model can call), and clients such as Claude Desktop, Cursor, or a custom agent discover those tools and invoke them during a run. For license verification, three patterns are in use.

1. Function calling straight to REST. If you run your own agent framework, you define a tool that calls the verification API over HTTPS. No MCP server needed; the agent's tool definition is the interface. This is the simplest path when you control the agent code.

2. Community MCP servers. Builders have already published MCP servers that wrap license lookups. One community server advertises 5M+ records across all 50 states. Another, contractor-license-mcp-server, exposes four focused tools: clv_verify_license, clv_batch_verify, clv_list_supported_states, and clv_search_by_name. A WA/CA-focused server built on official open data is also published as an Apify Actor that agents can call and pay for per successful verification. These are useful references, but community servers vary in data freshness, coverage depth, and maintenance, so evaluate them the way you would any dependency.

3. Your own thin MCP server over a normalized REST API. You write a small server, ten to thirty lines, that translates one tool call into one authenticated API request. You keep the API key server-side, you control the schema, and you own caching and logging. For teams shipping agents into production, this is the pattern that holds up. Our verification API and API docs are built for exactly this wrapper.

What should the tool schema look like?

Agents are only as reliable as the schema you hand them. One tool, strict types, normalized outputs:

{
  "name": "verify_contractor_license",
  "description": "Verify a contractor license in a US state. Returns normalized status, classifications, bond, insurance, and disciplinary flags.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "license_number": {
        "type": "string",
        "description": "License number exactly as issued, e.g. 1234567"
      },
      "state": {
        "type": "string",
        "description": "Two-letter state code, e.g. CA, TX, FL"
      }
    },
    "required": ["license_number", "state"]
  }
}

And the response the agent reasons over:

{
  "license_number": "1234567",
  "state": "CA",
  "status": "active",
  "business_name": "Acme Builders Inc",
  "classifications": ["B - General Building"],
  "bond": {"amount": 25000, "status": "on_file"},
  "insurance": {"general_liability": "on_file", "workers_comp": "exempt"},
  "disciplinary_actions": [],
  "checked_at": "2026-10-21T10:00:00-07:00"
}

Four design decisions matter. First, normalize status to a fixed enum (active, expired, suspended, revoked, not_found) instead of passing through each board's vocabulary; the agent cannot compare "clear" in one state against "current" in another. Second, always return checked_at: agents cache aggressively, and a timestamp is the only thing standing between a fresh check and a stale one. Third, return bond and insurance as structured objects, not prose; the agent needs to branch on bond.status == "cancelled", not parse a sentence. Fourth, offer a name-search variant that returns ranked candidates when the caller has a business name but no license number, and let the agent disambiguate with an address or a second data point.

How do you build a minimal MCP verification server?

A working sketch in Python, using the FastMCP helper. The API key lives in an environment variable; the agent never sees it. Adapt the request shape to your provider's docs.

import os
import httpx
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("license-verify")
API_KEY = os.environ["STATELICENSE_API_KEY"]

@mcp.tool()
def verify_contractor_license(license_number: str, state: str) -> dict:
    """Verify a contractor license. state is a two-letter code, e.g. CA, TX."""
    r = httpx.get(
        "https://api.statelicense.io/v1/licenses/verify",
        params={"license_number": license_number, "state": state.upper()},
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=20,
    )
    r.raise_for_status()
    return r.json()

Point your MCP client at this server (stdio transport for local use, Streamable HTTP when the agent runs remotely), and the model picks up verify_contractor_license alongside its other tools. From there, a prompt like "verify the license for Acme Builders, CA license 1234567, and tell me if anything is off" resolves to one tool call and a structured answer.

What breaks when agents call state boards directly?

It is tempting to let the agent browse to each board's lookup page. Five things go wrong.

Schema drift. Every board returns different fields under different names, and redesigns break scrapers without warning. As one MCP builder put it, "a JSON API doesn't break when someone redesigns the HTML." When your agent depends on fifty page layouts, every Tuesday is a potential outage.

Bot defenses. Board sites rate-limit, CAPTCHA, and block datacenter IPs. An agent mid-run that hits a challenge has no good recovery path; it either stalls or hallucinates a result.

Incomparable statuses. One board says "active," another says "clear," a third says "current." The agent cannot apply one policy across states without a normalization layer.

No batch path. Onboarding 200 subcontractors means 200 separate lookups. A verification API built for this supports batch checks; a board website does not.

Thin records. Many board status pages show a status and little else. Official open datasets can be richer (Washington L&I's dataset, for example, returns bond amounts and insurance carriers), but each one is a separate integration to build and maintain. A normalized API does that joining work once, for every caller. Our pillar guide to contractor license verification APIs walks through what the normalized record contains and why each field matters.

How do agents keep verifications fresh?

A verification is a snapshot, not a subscription. Licenses lapse, bonds get cancelled, and workers' comp policies expire mid-project. Agents that verify once at onboarding and never again will eventually approve a contractor whose coverage quietly died.

The fix is scheduled re-verification: the agent re-runs verify_contractor_license on a cadence tied to risk (monthly for active subs, before every draw for lenders), diffs the new record against the last one, and escalates on change. Our guide to contractor license monitoring covers alert design and recheck cadences in detail.

FAQ

Does the agent need its own API key? No. Keep the key in the MCP server's environment. The agent only sees the tool definition and its inputs; credentials never enter the model's context.

What does verification cost at agent scale? Agents make speculative calls: they check first and decide later. Budget per verification accordingly, cache results for the session, and re-verify on a schedule rather than on every reasoning step. StateLicense offers a free tier with Pro at $49/mo for higher volume; see pricing.

Can the agent search by business name instead of a license number? Yes, with a name-search tool that returns ranked candidates. The agent then disambiguates using address, phone, or a license number from a second source before treating any candidate as verified.

Which states can an agent verify? That depends on the provider behind the tool. Community servers range from two states to all fifty, with varying depth. StateLicense covers CA, FL, NYC, OR, TX, VA, and WA; see the coverage page for methodology.

Checklist: license verification your agents can trust

  1. Give the agent one tool with a strict schema, not fifty board websites.
  2. Normalize license status to a fixed enum before the agent reasons over it.
  3. Return bond, insurance, and disciplinary data as structured fields, not prose.
  4. Always include checked_at, and cache by (license number, state) for the session.
  5. Keep API keys server-side in the MCP server; never in prompts.
  6. Re-verify on a schedule tied to risk, and alert on status changes.