# Michigan Contractor License Lookup Scraper (`automation-lab/michigan-contractor-license-lookup`) Actor

Look up supplied Michigan residential builder person names in official LARA records. Export visible license identity, expiration, locality and query provenance; bounded first result table, without active-status or census claims.

- **URL**: https://apify.com/automation-lab/michigan-contractor-license-lookup.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 item extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Michigan Contractor License Lookup Scraper

Run a **michigan contractor license lookup** for supplied person names against the official Michigan LARA **Residential Builder Individual** registry. Export the visible license number, holder, expiration, locality and query provenance for roster review.

This is a bounded name-lookup tool, not an all-professions database or a certification that a contractor is active. It reads the first visible result table for each name. It does not follow pagination or open detail pages.

### Who is it for?

Procurement teams can check a short residential-builder roster before manual credential review. Compliance analysts can export a consistent table instead of transcribing search results. Operations teams can schedule the same supplied names and compare snapshots outside this Actor.

### Why use this Actor?

- Uses the official public LARA source rather than an unrelated contractor directory.
- Keeps license numbers as text and preserves the source's expiration text.
- Carries the submitted name alongside each record for traceability.
- Deduplicates overlapping name queries and provides a global output cap.
- Fails on unrecognized responses instead of treating a broken page as a clean search.

A partial name match is not proof of identity. Review ambiguous names and expired records against the regulator before making a decision.

### Getting started

1. Open the Input tab.
2. Supply one to 20 person-name objects in `queries`.
3. Set `maxItems` to bound the total unique output.
4. Run the Actor and inspect the dataset plus `SUMMARY` in the key-value store.
5. Export JSON, CSV or Excel using Apify's dataset export controls.

```json
{
  "queries": [{"firstName": "Albert", "lastName": "Smith"}],
  "maxItems": 10
}
```

The default example returns records whose expiration can be historical. It is a useful extraction example, not a recommendation to hire anyone.

### Inputs and matching semantics

| Input | Meaning |
|---|---|
| `queries` | Required array of 1–20 objects, processed in input order. |
| `lastName` | Required surname, 2–70 characters after trimming. |
| `firstName` | Optional given name, up to 70 characters after trimming. |
| `maxItems` | Global unique-license cap, default 100, allowed 1–1000. Zero is invalid. |

Name values are submitted to the official first/last-name fields. The source can return partial-name matches, including longer surnames. This Actor does not apply exact whole-name equality, linguistic word matching or an active-status filter. Case handling belongs to the registry. Leading/trailing whitespace is removed; wildcard and control characters are rejected.

Business names, license identifiers, arbitrary URLs, profession selection and unsupported input keys are rejected. Only Residential Builder Individual is queried.

### Coverage and limits

Each query exports only the first result table returned by the source. A broad name can have additional uncollected matches. Narrow the supplied first and last name when possible. Raising `maxItems` does not expand upstream coverage.

The cap applies after deduplication across all queries. Once it is reached, later queries are not searched. A license found by multiple queries appears once, with the first query's provenance. `SUMMARY` reports requested/completed queries, saved rows and the coverage statement.

No complete Michigan contractor census, active-only result set, history, notifications, automatic change detection or all-trades coverage is provided. Scheduling and snapshot comparison are downstream workflows.

### Extracted fields

| Field | Description |
|---|---|
| `licenseNumber` | Visible credential identifier as text. |
| `licenseType` | Residential Builder Individual. |
| `holderName` | Joined visible first, middle and last name. |
| `firstName`, `middleName`, `lastName` | Separate source holder-name columns. |
| `businessName` | Visible business name, otherwise null. |
| `expirationDate` | Source MM/DD/YYYY text, otherwise null; not license status. |
| `city`, `state`, `postalCode` | Visible locality values, otherwise null. |
| `queryFirstName`, `queryLastName` | Trimmed supplied query that first found this license. |
| `sourceUrl` | Official search page; individual result links require session state. |
| `scrapedAt` | UTC extraction timestamp. |

### Output example

The following credential identity and expiration were observed in the public result table; timestamps vary by run. Optional business names can be absent.

```json
{
  "licenseNumber": "2101000352",
  "licenseType": "Residential Builder Individual",
  "holderName": "Albert A Smith",
  "firstName": "Albert",
  "middleName": "A",
  "lastName": "Smith",
  "businessName": null,
  "expirationDate": "05/31/1993",
  "city": "Southfield",
  "state": "MI",
  "postalCode": "48076",
  "queryFirstName": "Albert",
  "queryLastName": "Smith",
  "sourceUrl": "https://aca-prod.accela.com/LARA/GeneralProperty/PropertyLookUp.aspx?isLicensee=Y",
  "scrapedAt": "2026-10-23T07:00:00.000Z"
}
```

### How much does it cost to look up Michigan builder licenses?

The Actor uses pay-per-event pricing: a start event plus one `item` event per unique license saved. Empty searches produce no item events; the start event still applies. Repeated licenses within a run have no separate item charge. Consult the current Pricing tab for plan-specific rates.

The start fee is **$0.005 per run**. Per-license prices are FREE **$0.0046**, BRONZE **$0.004**, SILVER **$0.00312**, and GOLD / PLATINUM / DIAMOND **$0.0024**. Tiers follow your qualifying aggregate monthly Apify Store spend, not a private per-Actor volume schedule.

At BRONZE, estimated Actor charges are $0.009 for one result, $0.025 for five, $0.045 for ten, $0.105 for 25 and $0.405 for 100. These are estimates, not guaranteed invoices or payouts; refunds, fraud, disputes, taxes, corrections and clawbacks can affect finalized amounts. Platform or account charges may apply separately. No user-supplied proxy or external paid API key is required.

### Integrations

Export the dataset to a spreadsheet for manual credential review. Send JSON to a procurement system, joining on license number rather than person name. With an Apify schedule, run the same roster periodically and compare `expirationDate` and locality columns in your own database. Record missing matches separately: a capped or partial-name search is not proof that a license was revoked.

### API usage

Use an Apify API token in an environment variable. Start one run and keep its run ID; do not restart merely to check completion.

```bash
curl -X POST \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  'https://api.apify.com/v2/acts/automation-lab~michigan-contractor-license-lookup/runs' \
  -d '{"queries":[{"firstName":"Albert","lastName":"Smith"}],"maxItems":10}'
```

JavaScript with the official client:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/michigan-contractor-license-lookup')
  .call({ queries: [{ firstName: 'Albert', lastName: 'Smith' }], maxItems: 10 });
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 10 });
console.log(items);
```

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/michigan-contractor-license-lookup').call(
    run_input={'queries': [{'firstName': 'Albert', 'lastName': 'Smith'}], 'maxItems': 10})
print(client.dataset(run['defaultDatasetId']).list_items(limit=10).items)
```

### MCP setup

Select a stable Actor-specific subset:

```bash
claude mcp add --transport http apify \
  'https://mcp.apify.com?tools=automation-lab/michigan-contractor-license-lookup'
```

Claude Desktop, Cursor and VS Code configuration (use the client's supported remote MCP adapter):

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/michigan-contractor-license-lookup"}}}
```

Authenticate using the client's supported Apify flow. Discover names and argument schemas with scoped `tools/list`. Actor selection also adds run-status, dataset, key-value-store and abort tools; discovery alone is not permission to execute them.

Example prompts: “Look up Albert Smith's Michigan residential builder records, return at most 10 licenses, and explain the visible-table and status limitations.”

For an authorized invocation, start once and retain the returned run/storage IDs. These are metadata, not records. `call-actor` waits at most 45 seconds (default 30). If still running, follow that same run with `get-actor-run`, using 2/4/8-second backoff capped at 10 seconds and a 120-second total deadline. Set the client timeout above the chosen server wait plus transport margin. Recover a known run after client timeout; never blindly rerun an unknown outcome. At the deadline report the pending run ID.

After success, read `get-dataset-items` with explicit offset/limit/fields. A consumer can use 20-row pages, at most 100 source rows and 64 KiB of serialized UTF-8 content admitted to model context, whichever comes first. Enforce byte limits host-side, disclosing omitted oversized content; clients without interception cannot guarantee that bound. Track source offsets, report truncation/continuation, and keep full exports outside model context. Failed-run rows are partial. These consumer limits do not change Actor coverage. Tool discovery/cache behavior depends on the client; no numeric token savings are promised.

### FAQ and troubleshooting

An explicit source no-results message produces a successful empty dataset and summary. Network, challenge, changed form and malformed table errors fail the run. Previously saved rows may be partial on failure. Each query uses a fresh cookie/form-state session. One retry is allowed for transient transport errors, 429 and 5xx; no automatic paid proxy or browser fallback is enabled.

**Why did I get an old expiration date?** The result table includes historical credentials. Expiration is not a current-status flag.

**Why did a longer surname match?** The registry can use partial-name matching. Supply a first name and manually verify identity.

**Why are fewer rows returned than the cap?** The cap is a maximum, not a target. The source's visible first table bounds coverage.

### Legality, responsible use and privacy

Use public licensing information for lawful, proportionate verification. Respect source terms and applicable privacy law. This Actor is not affiliated with Michigan LARA and does not provide legal advice or replace an official determination.

The Actor does not use AI to process inputs or source records. Cookies and form state are ephemeral per query; there is no cross-run cache. Apify datasets, KV stores and logs follow your account retention settings and can be deleted with Apify storage controls. For help, open an issue on this Actor's Apify page.

Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL query strings are removed; reports are retained for 30 days. Do not submit private credentials or unnecessary personal information.

### Support

When reporting a problem through the Actor's Apify issue tab, include the run link, expected credential category and whether the response was empty, partial or failed. Share private input only through appropriate private support controls. Do not post credentials in public comments. A changed regulator page is treated as a source error, not as evidence that a license ceased to exist.

### Related Actors

For a different jurisdiction, see [North Carolina Contractor License Lookup](https://apify.com/automation-lab/north-carolina-contractor-license-lookup-scraper). Its source and coverage are separate; Michigan names are not searched there.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/michigan-contractor-license-lookup/changelog.md

# Actor input Schema

## `queries` (type: `array`):

One to 20 objects with required lastName (2–70 characters) and optional firstName (up to 70 characters). Whitespace is trimmed. Names are submitted to the official first/last-name fields; the registry can return partial-name matches, not exact identity matches. Only Residential Builder Individual is searched. No business-name or license-number mode. Each query reads only the visible first result table; overlapping licenses are deduplicated by license number with the first query retained.

## `maxItems` (type: `integer`):

Global run cap after deduplication, across queries in input order. Default 100; 1–1000 only (zero is invalid). Stops processing later queries when reached. Does not expand the upstream visible table, follow pagination, or guarantee that many rows. SUMMARY reports completed query count.

## Actor input object example

```json
{
  "queries": [
    {
      "firstName": "Albert",
      "lastName": "Smith"
    }
  ],
  "maxItems": 10
}
```

# Actor output Schema

## `overview` (type: `string`):

Unique licenses from the first visible result table of supplied person-name queries.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        {
            "firstName": "Albert",
            "lastName": "Smith"
        }
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/michigan-contractor-license-lookup").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "queries": [{
            "firstName": "Albert",
            "lastName": "Smith",
        }],
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/michigan-contractor-license-lookup").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    {
      "firstName": "Albert",
      "lastName": "Smith"
    }
  ],
  "maxItems": 10
}' |
apify call automation-lab/michigan-contractor-license-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/michigan-contractor-license-lookup"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/gUmOfa24purmxZdMh/builds/7eFQvAT9srr2eyJo4/openapi.json
