# Google Maps Contractor License Check (`wellbuilt_zythem/google-maps-contractor-license-check`) Actor

Check contractors scraped from Google Maps against official state license registers (CA, AZ, WA, FL, TX and more). Each business gets license number, type, status, expiry and the register phone, matched by phone or name. Pay only for licenses found.

- **URL**: https://apify.com/wellbuilt_zythem/google-maps-contractor-license-check.md
- **Developed by:** [Carlos Escanez](https://apify.com/wellbuilt_zythem) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 license found in the state registers

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

## Google Maps Contractor License Check

**Bring the contractors you scraped from Google Maps. Get back, for each one, the license the state board has on file: number, type, status, expiry date, and the name and phone in the register.**

Google Maps tells you a roofer exists. It does not tell you whether that roofer holds a contractor license. This Actor takes your Google Maps results (the dataset of any Google Maps scraper run, or a pasted list) and checks every business against the **official state contractor license register**, by phone number first and then by business name.

You pay only for businesses where a license is found. Businesses with no license found, and businesses that cannot be checked (wrong state, not a licensed trade), come back with the reason and are **not charged**.

### Who uses it

- **Lead-gen and marketing agencies** that sell to contractors: keep the licensed ones, drop the lead-gen fronts that rank on Maps with an 800 number.
- **Insurers, bonding companies and lenders**: confirm license number, type and expiry before quoting.
- **Suppliers and distributors** opening trade accounts: check the license on file in seconds, in bulk.
- **Marketplaces and home-service platforms** vetting pros before listing them.

### Measured on real Google Maps data

Roofers taken from Google Maps searches in six metros, checked on 17 October 2026. "Checkable" means a roofing business in a state where this Actor reads the roofing list.

| Metro | Roofers checkable | License found | How |
|---|---|---|---|
| Phoenix, AZ | 16 | **11** | by name (the Arizona ROC does not publish phone numbers) |
| San Diego, CA | 13 | **7** | 5 by phone, 2 by name |
| Miami, FL | 6 | **4** | by name (the DBPR does not publish phone numbers) |
| Seattle, WA | 6 | **3** | by phone and name |
| Los Angeles, CA | 16 | **2** | by phone |
| Dallas, TX | 0 | not checkable | Texas has no state roofing license list in this Actor |

Across the four states: **27 licenses found for 57 roofers (47 %)**. Los Angeles is the outlier: 14 of its 16 Google "roofers" match no license under their name or phone (3 of those 14 list a toll-free or out-of-state number).

### How a business is matched

1. **State** from the business address, **trade** from the Google category (Roofing contractor, Electrician, Plumber, HVAC contractor, General contractor...), or one trade you pick for the whole list.
2. The register lists for that state and trade are downloaded once per run. A roofer is also checked against the general building lists, because many roofers work under a general contractor license (in California, class B instead of C-39).
3. **Phone match** (same 10 digits) is the strongest evidence. Then **name match**: identical names, or one name contained in the other ("Smith Roofing - Roof Repair San Diego" against "SMITH ROOFING INC"). A partial name match only counts when the city or ZIP code is the same.
4. Names that only share trade words or a place name never match: "ABC Roofing" is not "XYZ Roofing", and "The Seattle Roofing Company" is not "Seattle Roofing Solutions".

Every found row says **how** it matched (`matchedBy`: `phone+name`, `phone` or `name`) and **how sure** it is (`matchConfidence`: `high` or `medium`), so you can filter.

### Coverage by state

| State | Board | Trades checked | Phone in register |
|---|---|---|---|
| Arizona | Registrar of Contractors (ROC) | roofing, general, electrical, plumbing, HVAC, pool, painting, concrete, landscaping | no |
| Arkansas | Contractors Licensing Board | roofing, general, electrical, plumbing, HVAC, pool, painting, concrete, landscaping | yes (99 %) |
| Washington | Labor & Industries | all construction trades (one register) | yes |
| Oregon | Construction Contractors Board | all construction trades (one register) | yes |
| California | CSLB | roofing, general, electrical, plumbing, HVAC, pool | yes |
| Florida | DBPR | roofing, general, electrical, plumbing, HVAC, pool | no |
| Nevada | State Contractors Board | roofing, general, electrical, plumbing, HVAC, pool | yes |
| Alabama | Licensing Board for General Contractors | roofing, general, electrical, plumbing, HVAC, pool | yes |
| Louisiana | State Licensing Board for Contractors | roofing, general, electrical, plumbing, HVAC, pool | yes |
| Tennessee | Board for Licensing Contractors | roofing, general, electrical, plumbing, HVAC, pool | no |
| North Carolina | Licensing Board for General Contractors and trade boards | roofing, general, electrical, plumbing, HVAC, pool | 92 % |
| Michigan | LARA | roofing, general, electrical, plumbing, HVAC, pool | no |
| Connecticut | Department of Consumer Protection | roofing, general, electrical, plumbing, HVAC, pool | no |
| Delaware | Division of Revenue and Professional Regulation | roofing, general, electrical, plumbing, HVAC, pool | no |
| Virginia | DPOR Board for Contractors | roofing, general, electrical, plumbing, HVAC, pool | no |
| Minnesota | Department of Labor and Industry | roofing, general, electrical, plumbing, pool | 84 % |
| Texas | TDLR and TSBPE | electrical, plumbing, HVAC | partly |
| Ohio | OCILB | electrical, plumbing, HVAC | 70 % |
| Oklahoma | Construction Industries Board | roofing | yes |

Match rates were measured in Arizona, California, Florida and Washington. The other states use the same registers as the [US Contractor License Lookup](https://apify.com/wellbuilt_zythem/us-contractor-license-lookup) Actor and the same matching, but have not been measured with Google Maps data yet. Where the register has no phone, matching is by name only and fewer businesses are found.

**Not covered:** states not in the table (Georgia, Illinois, Colorado, New York State trade licenses and others) and New York City licenses. Businesses there come back as not checkable, with the reason.

### Input

Pick **one** of:

- **Google Maps dataset**: the dataset of a Google Maps scraper run, chosen with the dataset picker. The Actor only gets read access to that dataset. Field names from the common scrapers are recognized: `title`, `phone` / `phoneUnformatted`, `address`, `city`, `state`, `postalCode`, `categoryName`, `categories`, `website`, `url`, `placeId`.
- **Pasted list**: JSON rows with at least a name and a phone or address.

```json
{
  "businesses": [
    { "title": "Pioneer Roofing Company", "phone": "+1 619-432-7663", "address": "3111 Camino Del Rio N #400, San Diego, CA 92108", "categoryName": "Roofing contractor" },
    { "name": "Trademark Roofing", "phone": "602-361-7618", "city": "Phoenix", "state": "AZ", "zip": "85016", "category": "Roofing contractor" }
  ],
  "trade": "auto",
  "maxBusinesses": 1000,
  "onlyLicensed": false
}
```

| Field | What it does |
|---|---|
| `mapsDataset` | Dataset of a Google Maps scraper run. |
| `businesses` | Pasted list. The example list in the form is ignored when you pick a dataset and leave it untouched. |
| `trade` | `auto` reads each Google category. Pick a trade to check every business as that trade (for lists without categories). |
| `maxBusinesses` | Upper limit of businesses read. Default 1,000. |
| `onlyLicensed` | Return only businesses with a license found. |

### Output

One row per business. A found license:

```json
{
  "businessName": "Pioneer Roofing Company",
  "phone": "+1619-432-7663",
  "city": "San Diego",
  "state": "CA",
  "category": "Roofing contractor",
  "trade": "Roofing",
  "checked": true,
  "licenseFound": true,
  "matchedBy": "phone+name",
  "matchConfidence": "high",
  "licenseNumber": "1060507",
  "licenseState": "CA",
  "licenseType": "Contractor (C39)",
  "licenseList": "Roofing",
  "licenseStatus": "ACTIVE",
  "licenseExpirationDate": "2027-11-30",
  "registerBusinessName": "Pioneer Roofing Company LLC",
  "registerPhone": "+16194327663",
  "registerCity": "San Diego",
  "licenseBoard": "Contractors State License Board (CSLB)",
  "charged": true
}
```

No license found (not charged):

```json
{
  "businessName": "RSI Roofing",
  "state": "CA",
  "trade": "Roofing",
  "checked": true,
  "licenseFound": false,
  "listsChecked": "Roofing; General Building / Residential Remodeling",
  "note": "No license under this phone or name in the Contractors State License Board (CSLB) lists checked. It may hold one under another name or phone.",
  "charged": false
}
```

Not checkable (not charged): `checked: false`, `licenseFound: null` and `notCheckedReason`, for example *"This Actor has no roofing license list for Texas."*

Other columns: `address`, `zip`, `website`, `googleMapsUrl`, `placeId`, `rating`, `reviewsCount`, `inputIndex` (position in your input), `licenseActive`, `licenseDaysToExpiry`, `licenseIssuedDate`, `registerName`, `registerAddress`, `registerZip`, `registerCounty`, `licenseSourceUrl`, `otherLicenses` (other license numbers that also matched), `checkedAt`.

The run summary (key-value store, `RUN_SUMMARY`) lists per state: businesses, licenses loaded from the register, lists read or failed, found and not found.

### Pricing

Pay per event: one **license found** event per business matched to a license. Businesses with no license found and businesses that cannot be checked are delivered without charge. A run over 1,000 roofers in a state where half are found charges about 500 events.

### Said plainly

- **"No license found" does not mean unlicensed.** The business may hold a license under a different legal name or phone, or with a qualifier in another county. It means: no license under this phone or name in the lists checked, which the row names.
- **Name matches are medium confidence** unless the city or ZIP code also agrees. Filter on `matchConfidence` if you need only phone or same-city matches.
- **What "found" means depends on the register.** California's and Arizona's lists hold active licenses only, so a match there is an active license. Washington's register also keeps expired, suspended and out-of-business registrations (81,000 of its 149,000 construction rows on 17 October 2026), so a match there can be a license that is no longer active. `licenseStatus` and `licenseActive` always say which.
- **Florida is slow.** The DBPR register lists licenses by county and type, page by page: the Miami-Dade roofing and general contractor lists together (15,239 licenses) took 4.8 minutes to read in our test run. So in Florida, as in Louisiana, North Carolina and Michigan, only the trade's own list is read (the roofing list for a roofer); in Miami the general contractor list added no match. Each state has a 12-minute budget per run; a list that does not finish is reported in the row (`listsChecked`), and businesses that depended only on it come back as not checkable.
- **California general contractors (class B)** are downloaded by county, chosen from the businesses' ZIP codes. A California business without a ZIP code is checked against the trade list only.
- **Memory.** Each state's register is read on its own and released before the next one, so the default 1 GB is enough: the largest register, Washington's 149,000 rows, peaked at 448 MB. If a row ever says a register did not fit in the run's memory, run again with 2048 MB.
- **Businesses with no address on Google** (service-area businesses) have no state to check. Add `state` in a pasted list to check them.
- **Texas roofers and general contractors are not checkable**: Texas has no state license for either, and this Actor reads no Texas list for them. Texas electricians, plumbers and HVAC contractors are checked.

### Related

- [US Contractor License Lookup](https://apify.com/wellbuilt_zythem/us-contractor-license-lookup): look up or export the same registers by name, license number, state and trade.

# Actor input Schema

## `mapsDataset` (type: `string`):

The dataset of a Google Maps scraper run (for example Google Maps Scraper by compass, or any scraper that saves title, phone, address and category). This Actor only gets read access to the dataset you pick. Leave empty to use the list below.

## `businesses` (type: `array`):

Rows from a Google Maps scraper as JSON, or your own list with name (or title), phone, address (or city, state, zip) and category. If you pick a dataset above and leave this example list untouched, the example is ignored and not charged.

## `trade` (type: `string`):

Which license to look for. "From the Google category" reads each business category (Roofing contractor, Electrician, Plumber...). Pick a trade to check every business as that trade, for lists without categories.

## `maxBusinesses` (type: `integer`):

Upper limit of businesses read from the dataset and the list together.

## `onlyLicensed` (type: `boolean`):

Off: every business comes back, with licenseFound true, false or null (not checkable) and the reason. On: only the ones with a license found.

## Actor input object example

```json
{
  "businesses": [
    {
      "title": "Reimagine Roofing",
      "phone": "+1480-900-7663",
      "address": "1100 E Washington St #200, Phoenix, AZ 85034, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "Lyons Roofing",
      "phone": "+1602-276-5515",
      "address": "878 W Illini St, Phoenix, AZ 85041, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "Trademark Roofing",
      "phone": "+1602-361-7618",
      "address": "2942 N 24th St Unit 114, Phoenix, AZ 85016, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "San Diego County Roofing & Solar",
      "phone": "+1855-732-6868",
      "address": "1761 Hotel Cir S Ste 110, San Diego, CA 92108, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "Pioneer Roofing Company",
      "phone": "+1619-432-7663",
      "address": "3111 Camino Del Rio N #400, San Diego, CA 92108, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "RSI Roofing",
      "phone": "+1858-278-7200",
      "address": "8285 Buckhorn St, San Diego, CA 92111, United States",
      "categoryName": "Roofing contractor"
    }
  ],
  "trade": "auto",
  "maxBusinesses": 1000,
  "onlyLicensed": false
}
```

# Actor output Schema

## `results` (type: `string`):

One row per business: the Google Maps data plus licenseFound, how it matched (phone or name) and the license from the state register (number, type, status, expiry, name, phone, address). Not-checkable businesses say why.

## `runSummary` (type: `string`):

Per state: businesses checked, licenses loaded from the register, lists read or failed, licenses found and not found.

# 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 = {
    "businesses": [
        {
            "title": "Reimagine Roofing",
            "phone": "+1480-900-7663",
            "address": "1100 E Washington St #200, Phoenix, AZ 85034, United States",
            "categoryName": "Roofing contractor"
        },
        {
            "title": "Lyons Roofing",
            "phone": "+1602-276-5515",
            "address": "878 W Illini St, Phoenix, AZ 85041, United States",
            "categoryName": "Roofing contractor"
        },
        {
            "title": "Trademark Roofing",
            "phone": "+1602-361-7618",
            "address": "2942 N 24th St Unit 114, Phoenix, AZ 85016, United States",
            "categoryName": "Roofing contractor"
        },
        {
            "title": "San Diego County Roofing & Solar",
            "phone": "+1855-732-6868",
            "address": "1761 Hotel Cir S Ste 110, San Diego, CA 92108, United States",
            "categoryName": "Roofing contractor"
        },
        {
            "title": "Pioneer Roofing Company",
            "phone": "+1619-432-7663",
            "address": "3111 Camino Del Rio N #400, San Diego, CA 92108, United States",
            "categoryName": "Roofing contractor"
        },
        {
            "title": "RSI Roofing",
            "phone": "+1858-278-7200",
            "address": "8285 Buckhorn St, San Diego, CA 92111, United States",
            "categoryName": "Roofing contractor"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wellbuilt_zythem/google-maps-contractor-license-check").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 = { "businesses": [
        {
            "title": "Reimagine Roofing",
            "phone": "+1480-900-7663",
            "address": "1100 E Washington St #200, Phoenix, AZ 85034, United States",
            "categoryName": "Roofing contractor",
        },
        {
            "title": "Lyons Roofing",
            "phone": "+1602-276-5515",
            "address": "878 W Illini St, Phoenix, AZ 85041, United States",
            "categoryName": "Roofing contractor",
        },
        {
            "title": "Trademark Roofing",
            "phone": "+1602-361-7618",
            "address": "2942 N 24th St Unit 114, Phoenix, AZ 85016, United States",
            "categoryName": "Roofing contractor",
        },
        {
            "title": "San Diego County Roofing & Solar",
            "phone": "+1855-732-6868",
            "address": "1761 Hotel Cir S Ste 110, San Diego, CA 92108, United States",
            "categoryName": "Roofing contractor",
        },
        {
            "title": "Pioneer Roofing Company",
            "phone": "+1619-432-7663",
            "address": "3111 Camino Del Rio N #400, San Diego, CA 92108, United States",
            "categoryName": "Roofing contractor",
        },
        {
            "title": "RSI Roofing",
            "phone": "+1858-278-7200",
            "address": "8285 Buckhorn St, San Diego, CA 92111, United States",
            "categoryName": "Roofing contractor",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("wellbuilt_zythem/google-maps-contractor-license-check").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 '{
  "businesses": [
    {
      "title": "Reimagine Roofing",
      "phone": "+1480-900-7663",
      "address": "1100 E Washington St #200, Phoenix, AZ 85034, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "Lyons Roofing",
      "phone": "+1602-276-5515",
      "address": "878 W Illini St, Phoenix, AZ 85041, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "Trademark Roofing",
      "phone": "+1602-361-7618",
      "address": "2942 N 24th St Unit 114, Phoenix, AZ 85016, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "San Diego County Roofing & Solar",
      "phone": "+1855-732-6868",
      "address": "1761 Hotel Cir S Ste 110, San Diego, CA 92108, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "Pioneer Roofing Company",
      "phone": "+1619-432-7663",
      "address": "3111 Camino Del Rio N #400, San Diego, CA 92108, United States",
      "categoryName": "Roofing contractor"
    },
    {
      "title": "RSI Roofing",
      "phone": "+1858-278-7200",
      "address": "8285 Buckhorn St, San Diego, CA 92111, United States",
      "categoryName": "Roofing contractor"
    }
  ]
}' |
apify call wellbuilt_zythem/google-maps-contractor-license-check --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wellbuilt_zythem/google-maps-contractor-license-check"
        }
    }
}
```

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/aD8ecCRiCBzs8Pg9J/builds/4swDFscPYHatomwDL/openapi.json
