# US Active Contractors - Permits + License, Phone (`wellbuilt_zythem/us-active-contractors`) Actor

Contractors who pulled a building permit in the last 90 days in Seattle, Orlando, Cincinnati or Marin County, matched to their state licence: status, expiry date, phone. One row per contractor with permit count, total job value and lead score. 74 % matched in Seattle, measured.

- **URL**: https://apify.com/wellbuilt\_zythem/us-active-contractors.md
- **Developed by:** [Carlos Escanez](https://apify.com/wellbuilt_zythem) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 active contractors

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## US Active Contractors — who pulled a permit this quarter, with their state licence and phone

**One row per contractor who pulled a building permit in the last 90 days, joined to the state licensing board: licence number, status, expiry date, phone. With how many permits they pulled, the total declared job value, and their largest job.**

A permit is a job that just got funded. A licence is who is allowed to do it. Put together they answer a question no directory answers: **which contractors are working right now, are they licensed, and how do I reach them.**

```
Seattle, WA    | TK Elevator              | 11 permits | $34,147,832 | TKELEEC7933H ACTIVE, expires 2027-05-27 | +1 770 799 0498 | score 100
Orlando, FL    | Pulte Home Company LLC   | 144 permits | $20,611,237 | CBC1264055 ACTIVE, expires 2028-08-31  | +1 407 661 2102 | score 100
Cincinnati, OH | Triton Services          | 4 permits  | $1,439,700  | HV.25648 ACTIVE, expires 2028-09-30    | +1 513 679 6800 | score 96
```

Real rows from a test run on 15 September 2026.

### Where it works, measured

This Actor only covers cities where the join actually works, and says how well. The rate is the share of permit contractors that matched a licence on the state board, measured on the largest contractors of the last 45 days except where the row says otherwise.

| City | Licence board | Contractors matched | Phone on the licence |
|---|---|---|---|
| Seattle, WA | Washington L\&I | **67%** | yes, on every match |
| Orlando, FL | Florida DBPR | **71%** | no: Florida publishes none, the phone comes from the permit when Orlando records one (about 6 in 10 rows) |
| Cincinnati, OH | Ohio OCILB | **62%** for electrical, HVAC and plumbing; 9% for general and roofing, which Ohio does not license | yes, on 2 of 3 matches |
| Marin County, CA | California CSLB | **71%** over 90 days, 74% over 45 | yes, on every match |

**Why these cities.** The same join was measured in six others and failed for a reason no code fixes: the trades that pull permits there are licensed by a different board or by the city itself. New Orleans 5% (plumbers and electricians have their own boards), New York 1% (the city licenses, the state board only covers mould and elevators). Those cities are not offered rather than sold with an empty licence column. Austin measured 24% against electricians alone; with the TDLR air-conditioning file and the TSBPE plumber list added (25 September 2026) it reaches 76% on electrical, HVAC and plumbing permits, so it is offered with those three trades by default.

**Marin County needed a different way of asking, and here is what changed.** The CSLB hands out its register one classification at a time, and the general building list — 103,295 licences, 10 MB — answered with an HTTP 504 on three of four attempts on 17 September 2026. A run that lost it matched 54% of contractors instead of 71% and carried no general builders at all. The same list asked for one county at a time comes back in 218 KB in under ten seconds, every time. So that one classification is now fetched per county across the nine Bay Area counties, and every other classification is still fetched statewide, because the CSLB filters by the company's registered address and a contractor from outside the county still works here. County-scoping everything drops the join to 23%; scoping only the list that will not fit holds it at 71%, reproduced on three consecutive runs with no failed download.

### What every row carries

**From the permits:** `contractorName`, `permits` (count in the window), `totalJobValue`, `valuedPermits`, `firstPermitDate`, `lastPermitDate`, `permitTrades`, and the largest job: `largestPermitNumber`, `largestPermitAddress`, `largestPermitValue`, `largestPermitUrl`.

**From the licence:** `isLicensed`, `matchMethod` (`number` when the permit carried the licence number, `name` otherwise), `licenseNumber`, `licenseStatus`, `isActive`, `expirationDate`, `daysToExpiry`, `isExpiringSoon`, `licenseType`, `businessName`, `licensee`, `phone`, `phoneSource` (`licence` or `permit`), `email`, `address`, `licenseCity`, `zip`, `county`, `licenseUrl`.

**Computed:** `leadScore` 0–100. Active licence 35, phone 25, up to 20 for permit count, up to 10 for job value, 10 if the last permit is from the past 30 days. Rows come sorted by it.

### Who runs this on a loop

- **Building material suppliers and equipment rental** — the contractors pulling the most permits in your branch's city this quarter, with a phone that belongs to the licence, not to a directory.
- **Surety and insurance agents** — `isExpiringSoon` on a contractor with live permits is a renewal that cannot wait.
- **Marketing agencies selling to contractors** — `permits` high and no website is a client who is busy and invisible.
- **General contractors and developers** — is the sub who just bid on my job actually licensed, and how much work has it pulled lately.

### Filters

`cities`, `lastNDays` (default 90), `trades` (permit trade: roofing, solar, HVAC, electrical, plumbing, remodel, new construction, demolition, pool, fence), `minPermits`, `minJobValue`, `onlyLicensed` (default on), `onlyActive` (default on), `onlyWithPhone`, `maxContractorsPerCity` (default 300, largest job value first: Florida and Washington are checked one contractor at a time), `maxResults`.

Turn `onlyLicensed` off to see the contractors the board does not list: a trade the state does not license, or a name the board spells differently. Turn `onlyActive` off to see expired or suspended licences that are still pulling permits.

### What this does NOT give you

Written down so that nobody, and no AI agent reading this page, promises a buyer
something the sources do not publish.

- **Not every permit contractor matches a licence.** Two in three do, measured. The rest are a trade the state does not license, or a name the board spells differently. `onlyLicensed` is on by default so you are not charged for them; turn it off and they come back with the licence columns empty and `isLicensed: false`.
- **No phone from the Florida board.** Florida publishes none. In Orlando the phone comes from the permit when the city records one, and `phoneSource` says which row is which.
- **Email only where the board publishes one.** Washington, Florida and Ohio mostly do not, and California publishes none at all, so expect that column near-empty.
- **Four cities.** Not nine, not fifty. The others were measured and dropped: see above for what each one failed on.
- **Declared job value, not the contract.** It is what the permit application states, which is the number the city has, not what the contractor actually charged.
- **Permits, not bids.** The work is already permitted; it is not a tender you can bid on.
- **No homeowner names.** Contractor business data only.

Every run writes what it actually delivered into its log and run summary: how
many contractors matched, how many rows carry a phone, and how many of those
phones came from the licence rather than the permit.

***

### Pricing

Pay per contractor row delivered. Filtered-out rows cost nothing.

### Data sources

City permits from each city's official open-data API (City of Orlando, City of Seattle, City of Cincinnati, County of Marin). Licences from the Washington State Department of Labor & Industries, the Florida Department of Business and Professional Regulation, the Ohio Construction Industry Licensing Board and the California Contractors State License Board. All public regulatory records; contact details are the business contacts the boards and cities publish for that purpose.

The two halves are also sold on their own: [US Building Permits](https://apify.com/wellbuilt_zythem/us-building-permits) (seventeen registers, every permit) and [US Contractor License Lookup](https://apify.com/wellbuilt_zythem/us-contractor-license-lookup) (twenty states, every licence).

# Actor input Schema

## `cities` (type: `array`):

Leave empty for all five cities. Only cities where the state board actually licenses the trades that pull permits are offered, which is what makes the join work (Seattle 67%, Orlando 71%, Cincinnati 62%, Marin County 71%, Austin 76% of permit contractors matched, measured September 2026).

## `lastNDays` (type: `integer`):

The activity window. 90 days is a good picture of who is working right now; 30 days is who is working this month.

## `trades` (type: `array`):

Count only permits of these trades. Leave empty for every trade, except in Cincinnati and Austin, where empty means electrical, HVAC and plumbing: Ohio and Texas do not license general or roofing contractors, so those cannot be matched (9% measured) and are skipped unless you pick them.

## `minPermits` (type: `integer`):

2 or more drops the one-off jobs and keeps the contractors who are busy.

## `minJobValue` (type: `integer`):

Sum of the declared values of the contractor's permits in the window. 0 for no minimum.

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

Off to also get the contractors the board does not list (a trade it does not license, or a name that does not match); those rows carry the permit data and no licence columns.

## `onlyActive` (type: `boolean`):

Off to also see expired or suspended licences that are still pulling permits.

## `onlyWithPhone` (type: `boolean`):

For calling lists. Washington and Ohio publish a phone on most licences; Florida publishes none, so in Orlando the phone comes only from the permit when the city records one.

## `maxContractorsPerCity` (type: `integer`):

Contractors are checked against the board from the largest total job value down. Florida and Washington are checked one contractor at a time, so this caps the run time.

## `maxResults` (type: `integer`):

0 for no limit.

## `socrataAppToken` (type: `string`):

A free token from any city's open-data portal raises the permit API rate limit. Not needed for normal runs.

## Actor input object example

```json
{
  "cities": [
    "cincinnati"
  ],
  "lastNDays": 30,
  "trades": [],
  "minPermits": 1,
  "minJobValue": 0,
  "onlyLicensed": true,
  "onlyActive": true,
  "onlyWithPhone": false,
  "maxContractorsPerCity": 50,
  "maxResults": 25
}
```

# Actor output Schema

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

One row per contractor who pulled a permit in the window: permit count, total job value, last permit, plus the state licence matched to it (number, status, expiry, phone, email, address) and a 0-100 lead score.

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

Per city: permits read, contractors found, how many matched a licence, rows delivered and charged.

# 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 = {
    "cities": [
        "cincinnati"
    ],
    "lastNDays": 30,
    "maxContractorsPerCity": 50,
    "maxResults": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("wellbuilt_zythem/us-active-contractors").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 = {
    "cities": ["cincinnati"],
    "lastNDays": 30,
    "maxContractorsPerCity": 50,
    "maxResults": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("wellbuilt_zythem/us-active-contractors").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 '{
  "cities": [
    "cincinnati"
  ],
  "lastNDays": 30,
  "maxContractorsPerCity": 50,
  "maxResults": 25
}' |
apify call wellbuilt_zythem/us-active-contractors --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wellbuilt_zythem/us-active-contractors"
        }
    }
}
```

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/rbSynXlcc57Ybe775/builds/svULMAscCXnzPeosH/openapi.json
