# Kentucky Contractor License Lookup & Leads Scraper (`scrapebench/kentucky-contractor-license-lookup`) Actor

Contractors licensed by Louisville Metro and by the Bowling Green / Warren County Contractors Licensing Board, Kentucky: company, licence number, trade or licence type, expiry, street address, phone and e-mail. Export or look up.

- **URL**: https://apify.com/scrapebench/kentucky-contractor-license-lookup.md
- **Developed by:** [ScrapeBench](https://apify.com/scrapebench) (community)
- **Categories:** Lead generation
- **Security:** Limited permissions
- **Stats:** 5 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.002 / roofing contractor record

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

## Kentucky Contractor License Lookup & Leads Scraper

### Pain points

- Kentucky has no statewide general contractor licence; contractors are licensed by local boards, so a Kentucky contractor list has to be read board by board.
- The Board's public contractor list pages 25 firms at a time over about 117 pages and leaves out the licence number and the trades (checked 2026-11-06).
- The Board's search answers one name, trade, phone or licence number at a time, as an on-screen table with no download.
- Louisville Metro's licensee search stops at '100+' results and shows 10 rows a page, and its phone and e-mail sit on each licence's own page; its open-data contractor layer stopped being updated in 2022 (checked 2026-11-06).

### What we solve

- Two local rosters in one dataset, each row tagged with its locality: Bowling Green (2,906 rows, 2,888 licence numbers on November 6, 2026) and Louisville Metro (17 contractor licence types).
- One clean table: company, licence number, trades or licence type in the source's own words, expiry date and status derived from it, phone; owner names and cell for Bowling Green, street address and e-mail for Louisville.
- Louisville read by licence-number range so no search hits the 100-result cap, each delivered licence's own page opened for phone and e-mail, licences expired more than 3 years ago left out.
- Filter by locality, by trade or licence type (Bowling Green: General Contractor 718, Handyman 263, Electrician 245, Roofing 165 …; Louisville: Electrical Contractor, HVAC Contractor, Residential Building Contractor …) and by expiry date; look up names or licence numbers live on both boards' own searches.

### Summary

Kentucky contractor licences from two local rosters. Kentucky has no statewide general contractor licence; cities and counties license contractors. (1) The Bowling Green / Warren County Contractors Licensing Board: each row has the company, its owner name(s), the licence number (G… general, S… specialty), the trades the Board lists for it, the licence expiry, a phone and a cell. On November 6, 2026 the roster held 2,906 rows: a phone filled 2,895 (a cell on 1,071); the Board publishes no street address or email. (2) Louisville Metro's Accela Citizen Access licensee register, 17 contractor licence types (building, electrical, HVAC, fire detection and suppression, sign, wrecking …): licence number, licence type, business name, street address, licence expiry, and from each licence's own page the phone, e-mail and contact name. In 16 delivered Louisville rows read on November 6, 2026, every row had a street address, 12 a phone and 14 an e-mail. Neither board publishes a status: Active / Expired is derived from the expiry. Exports read a stored copy of each roster; lookups search both boards live. Pay only per licence returned.

### Who it's for

- Roofing, HVAC, plumbing and building-material suppliers selling to contractors working in Louisville, Bowling Green and Warren County
- Lenders, insurers and payroll providers reaching licensed local contractors
- Homeowners, property managers and general contractors checking a subcontractor's Louisville or Bowling Green licence and expiry
- Market researchers sizing the Kentucky trades

### How to use

This actor does two jobs. Run it, then collect results from the run's dataset (export to JSON/CSV/Excel, or pull via the Apify API).

**Get a list (export):** no names, just filters; returns the whole matching list.

```json
{
  "categories": [
    "ROOFING",
    "GENERAL CONTRACTOR"
  ],
  "maxResults": 1000
}
```

**Check specific firms (lookup):** put company names or licence numbers in `mode`, `queries`; **Inputs** below says how each is matched.

```json
{
  "mode": "lookup",
  "queries": [
    "Roofing",
    "G4527"
  ],
  "maxResults": 20
}
```

See **Inputs** below for every available field.

### What you get

One row per record:

| Field | Description |
|---|---|
| `license_number` | The board's licence number (Bowling Green: G… general, S… specialty; Louisville: 13808-BUS, LIC-MFL-21-00001 …); listing_id holds the same |
| `business_name` | Company as the board lists it; name holds the same. A Bowling Green licence can carry two trading names, one row each |
| `classifications` | Bowling Green: the trades the Board lists for the licence (GENERAL CONTRACTOR, ROOFING …); Louisville: the licence type |
| `license_type` | The first listed trade (Bowling Green) or the licence type (Louisville) |
| `person_name` | Owner name(s) as Bowling Green prints them; the contact's name from a Louisville licence's own page |
| `address` | Street address (Louisville only; Bowling Green publishes none) |
| `city` | City (Louisville only) |
| `zip_code` | ZIP code (Louisville only) |
| `expiration_date` | Licence expiry (YYYY-MM-DD) |
| `status` | Active or Expired, derived from the expiry date on the day the roster was read (neither board prints a status; a Louisville row with no expiry has none) |
| `phone` | Business phone; Bowling Green's cell where no phone is listed; Louisville's from the licence's own page |
| `phone_e164` | The same phone in +1XXXXXXXXXX form |
| `email` | E-mail, from a Louisville licence's own page, where Louisville Metro has one |
| `extras.cell_phone` | Cell phone, where the Bowling Green Board lists one beside the phone |
| `extras.licence_class` | General or Specialty, from a Bowling Green licence number's letter |
| `extras.locality` | The local board the licence comes from (Bowling Green or Louisville) |
| `source_url` | Bowling Green: the Board's contractor search tagged with the licence number (no per-licence page is public); Louisville: the licence's own page |

Sample:

```json
{
  "license_number": "S6527",
  "business_name": "AAA ROOFING CO INC",
  "classifications": [
    "ROOFING"
  ],
  "license_type": "ROOFING",
  "person_name": "HUNTINGTON, JAMES R/HUNTINGTON, DAVID M",
  "expiration_date": "2027-01-08",
  "status": "Active",
  "phone": "317-635-2928",
  "phone_e164": "+13176352928",
  "state": "KY",
  "source_url": "https://www.bgky.org/contractorslicensing/search#license-S6527-AAA%20ROOFING%20CO%20INC",
  "extras": {
    "locality": "Bowling Green",
    "licence_class": "Specialty",
    "cell_phone": "317-501-9112",
    "expires": "2027-01-08"
  }
}
```

### Inputs

| Field | Required | Type | Default | Description |
|---|---|---|---|---|
| `mode` | no | string | `"auto"` | export: no names, just filters; returns the whole matching list. lookup / verify: put company names (matched if contained in the company name) or licence numbers (exact) in 'queries'. auto picks the mode for you. |
| `query` | no | string | — | A business name (or listing number) to look up live. Kept alongside 'queries' for saved tasks. |
| `queries` | no | array | `[]` | A business name (or listing number) to look up live. Several values in one run; combined with 'query'. |
| `locality` | no | string | — | Only listings in this locality. Kept alongside 'localities' for saved tasks. |
| `localities` | no | array | `[]` | Only listings in this locality. Several values in one run; combined with 'locality'. |
| `status` | no | string | — | Only listings in this status. Kept alongside 'statuses' for saved tasks. |
| `statuses` | no | array | `[]` | Only listings in this status. Several values in one run; combined with 'status'. |
| `categories` | no | array | `[]` | Only rows from this trade or licence type (one or more). Available: ACOUSTICAL TREATMENTS, AIR - WATER FILTRATION, ASBESTOS - LEAD ABATEMENT, AUDIO - VIDEO HOME THEATER, AWNINGS - DOORS - WINDOWS, Building Contractor - Type B, CABINET INSTALLATION, CABLE INSTALLATION, CARPENTRY, CARPET INSTALLATION, CERAMIC TILE, CLEANING SERVICE, COMMUNICATIONS, CONCRETE - FOUNDATION, CONSTRUCTION MANAGEMENT, CONVEYING SYSTEMS, COUNTERTOP INSTALLATION, Commercial Building Contractor, Contractor, DECKING, DEMOLITION, DRILLING  -  BORING, DRYVIT - STUCCO, DRYWALLING, ELECTRICIAN, … (90 in all; an unknown name returns the full list, free). |
| `licenseTypes` | no | array | `[]` | Same filter as 'Trade or licence type', under the name every state licence actor takes; the two lists are combined. |
| `expiresAfter` | no | string | — | Only licences expiring on or after this date (YYYY-MM-DD). Licences with no published expiry are left out when set. |
| `expiresBefore` | no | string | — | Only licences expiring on or before this date (YYYY-MM-DD): renewals coming due. |
| `maxResults` | no | integer | `500` | Per query in lookup mode; per run in export mode. |
| `proxyConfiguration` | no | object | `{"useApifyProxy": false}` | Used only for live lookups; exports read the stored snapshot. |

### Pricing (Pay Per Event)

You pay per result (`dataset-item`) — **no charge for empty runs**. Example: **1000 license matches** at *$0.004/result* ≈ **$4.00**.

Notices (unknown trade, no match, source unavailable) are free. Apify platform usage is billed separately per your plan.

### Use cases

- Sales lists: every licensed roofer or HVAC contractor in Bowling Green, with owner name and phone.
- Louisville outreach: electrical, HVAC or building contractors with an unexpired Louisville Metro licence, with street address, phone and e-mail.
- Renewal campaigns: licences expiring before a date (457 of the roster's rows expired by January 31, 2027, as read on 2026-11-06).
- Subcontractor checks: confirm a licence number, its trades and its expiry before work starts.
- Market sizing: count licensed contractors by trade, general against specialty.

### Why this actor

- Two local rosters in one dataset, with the licence numbers and trades Bowling Green's list pages leave out.
- Bowling Green: a phone on 2,895 of 2,906 rows, a cell on 1,071 (2026-11-06). Louisville: street address, plus phone and e-mail from each licence's own page.
- Filters the boards' sites lack: locality, several trades or licence types at once and expiry dates.
- Pay per licence; notices are free.

### Limitations & updates

Covers two local rosters, not the rest of Kentucky: the Bowling Green / Warren County Contractors Licensing Board (2,906 rows on 2026-11-06; no street address or email; current licences only) and Louisville Metro's 17 contractor licence types (licences expired more than 3 years ago left out). Status is derived from the expiry date. Louisville's phone and e-mail are read per delivered row from each licence's page; if the run's time runs out first, the remaining rows carry the list fields only and a free notice says how many. Exports come from a stored copy (its date is in the run log).

### FAQ

**Where does the data come from?**

The Active Contractor Search of the Bowling Green / Warren County Contractors Licensing Board (bgky.org/contractorslicensing) and Louisville Metro's Accela Citizen Access licensee search (aca-prod.accela.com/LJCMG). Exports read a stored copy of each roster (the run log gives its date); lookups search both boards live and fall back to the stored copy when either does not answer. Louisville's phone and e-mail are read from each delivered licence's own page.

**Does it cover all of Kentucky?**

Not yet. Kentucky has no statewide general contractor licence: cities and counties license contractors, and the state board licenses individual trades (electrical, HVAC, plumbing) without publishing contact details. This actor reads Louisville Metro and the Bowling Green / Warren County board; set Locality to pick one. Lexington is not included.

**Is there a street address or email?**

For Louisville, yes: every row has the street address, and the phone and e-mail come from each licence's own page where Louisville Metro has them. Bowling Green publishes the company, owner names, licence number, trades, expiry, a phone and a cell; no address or email.

**Are expired licences included?**

Bowling Green lists current licences only. Louisville's register reaches back years: exports leave out licences that expired more than 3 years ago and keep the rest. Status (Active / Expired) is derived from the expiry date (neither board prints one); set Status to Active, or filter on Expires on or after / before to target renewals. Louisville's generic 'Contractor' type prints no expiry, so those rows have no status.

**How do I look up a firm or a licence number?**

Put company names or licence numbers in queries and set mode to lookup; both boards are searched and their answers interleaved. Bowling Green: a name matches if it is contained in the company name, a number (G4527) matches exactly. Louisville: a name or number matches from the start of the board's own value (13808-BUS).

**How current is it?**

Exports come from a stored copy of the roster, refreshed when it is over 30 days old and the actor is in use; the run log states its date, and a copy older than 45 days adds a free notice saying so. Lookups read the Board's current search.

**How am I charged?**

One event per licence returned. Notices are free.

### Guides & use cases

Written up on **[scrapebench.dev](https://scrapebench.dev)** — the bench that runs and verifies this actor against the live source every night:

- **How-to:** [How to run Kentucky Contractor License Lookup & Leads Scraper](https://scrapebench.dev/guides/how-to-kentucky-contractor-license-lookup/)
- **Use case:** [Bowling Green KY Licensed Contractors (all trades)](https://scrapebench.dev/use-cases/bowling-green-ky-licensed-contractors/)

More actors, coverage and nightly verification results: **[scrapebench.dev](https://scrapebench.dev)**

### Works with AI assistants (MCP)

Callable as an MCP tool, so Claude, Cursor, VS Code Copilot and other MCP clients can run it directly. Grab the config from the [MCP tab](https://apify.com/scrapebench/kentucky-contractor-license-lookup/api/mcp) on this page — Apify hosts the server and keeps that snippet current, and OAuth signs you in on first connect, so no API token goes in your config file.

Then just ask:

> "List roofing contractors licensed in Bowling Green, Kentucky, with their phone numbers and licence expiry."

That is Trade ROOFING (165 licences on 2026-11-06). Keep maxResults small on a first call. Runs started this way bill exactly like any other run.

### Data & lawful use

This actor collects publicly available information that the Bowling Green / Warren County Contractors Licensing Board (Bowling Green, Kentucky) and Louisville / Jefferson County Metro Government (Louisville Metro) publishes for public
lookup — licence records, business listings and the contact details professionals list there.
Some of it identifies individuals (sole proprietors, licence holders, agents), so treat any
names, phone numbers and emails in your results as personal data.

When you run this actor, you decide what is collected and what it is used for, so you are the
controller of that data and we act only as your processor (Apify Standard Actor Contract).
You need a lawful basis for your use, must honour opt-out and deletion requests, and must
follow the marketing rules that apply to you (CAN-SPAM, TCPA, GDPR, CCPA and similar).
Don't use the output for unsolicited bulk messaging, for screening covered by the FCRA, or
for anything the source's own terms forbid.

Your results are stored in your own Apify account. We don't keep a copy of your results and
don't sell or share them.

### Disclaimer

This is an independent tool. It is not affiliated with, endorsed by or sponsored by
the Bowling Green / Warren County Contractors Licensing Board (Bowling Green, Kentucky) and Louisville / Jefferson County Metro Government (Louisville Metro). Names and marks are used only to describe the data source. Records are
provided as published by the source and may be out of date or incomplete. For anything
consequential (hiring, a permit, a legal decision), confirm the record with the issuing
agency. You are responsible for making sure your use complies with applicable law and the
source's terms.

### Need help?

Open an issue on this actor's **Issues** tab and we'll respond there. Bug reports, missing
fields and feature requests are all welcome. Guides and examples: [scrapebench.dev](https://scrapebench.dev).

If you are a person named in this data and want your record removed from our copy, email
apify-help@scrapebench.dev; the source agency remains the official record.

Your run may be used to improve our services. If something looks off, or you want this actor to fit your use better, please share your run data with us: [Apify Console → Settings → Security → Share run data with developers](https://console.apify.com/settings/security). It lets us see exactly what went wrong and keep improving the actor.

If this actor saved you time, a short review on its Store page helps other buyers find it.

# Actor input Schema

## `mode` (type: `string`):

export: no names, just filters; returns the whole matching list. lookup / verify: put company names (matched if contained in the company name) or licence numbers (exact) in 'queries'. auto picks the mode for you.

## `query` (type: `string`):

A business name (or listing number) to look up live. Kept alongside 'queries' for saved tasks.

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

A business name (or listing number) to look up live. Several values in one run; combined with 'query'.

## `locality` (type: `string`):

Only listings in this locality. Kept alongside 'localities' for saved tasks.

## `localities` (type: `array`):

Only listings in this locality. Several values in one run; combined with 'locality'.

## `status` (type: `string`):

Only listings in this status. Kept alongside 'statuses' for saved tasks.

## `statuses` (type: `array`):

Only listings in this status. Several values in one run; combined with 'status'.

## `categories` (type: `array`):

Only rows from this trade or licence type (one or more). Available: ACOUSTICAL TREATMENTS, AIR - WATER FILTRATION, ASBESTOS - LEAD ABATEMENT, AUDIO - VIDEO HOME THEATER, AWNINGS - DOORS - WINDOWS, Building Contractor - Type B, CABINET INSTALLATION, CABLE INSTALLATION, CARPENTRY, CARPET INSTALLATION, CERAMIC TILE, CLEANING SERVICE, COMMUNICATIONS, CONCRETE - FOUNDATION, CONSTRUCTION MANAGEMENT, CONVEYING SYSTEMS, COUNTERTOP INSTALLATION, Commercial Building Contractor, Contractor, DECKING, DEMOLITION, DRILLING  -  BORING, DRYVIT - STUCCO, DRYWALLING, ELECTRICIAN, … (90 in all; an unknown name returns the full list, free).

## `licenseTypes` (type: `array`):

Same filter as 'Trade or licence type', under the name every state licence actor takes; the two lists are combined.

## `expiresAfter` (type: `string`):

Only licences expiring on or after this date (YYYY-MM-DD). Licences with no published expiry are left out when set.

## `expiresBefore` (type: `string`):

Only licences expiring on or before this date (YYYY-MM-DD): renewals coming due.

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

Per query in lookup mode; per run in export mode.

## `proxyConfiguration` (type: `object`):

Used only for live lookups; exports read the stored snapshot.

## Actor input object example

```json
{
  "mode": "auto",
  "queries": [],
  "localities": [],
  "statuses": [],
  "categories": [],
  "licenseTypes": [],
  "maxResults": 500,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per business listing; notices are free.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapebench/kentucky-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("scrapebench/kentucky-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 '{}' |
apify call scrapebench/kentucky-contractor-license-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapebench/kentucky-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/d2F65kSQOZJrvFFFA/builds/a7khOHXzuPqUEHDMp/openapi.json
