How-to
How to run Pennsylvania Contractor Registration Lookup & Leads Scraper
Step-by-step: the exact input, what each field does, and the rows that come back from PA Attorney General HIC search.
Pennsylvania has no statewide contractor licence. What it has is a register: every contractor who does $5,000 or more of home improvement work a year signs up with the Office of Attorney General under the Home Improvement Consumer Protection Act. The Attorney General’s search is free and shows each contractor’s phone and address, and it can download an Excel file of a search’s results. It answers one county at a time, though: a search with no county and no name returns nothing, and each search first asks you to prove you are human. For one county and a one-off list that is fine. People open this actor when they want several counties or the whole state in one file, or want to check a batch of registration numbers.
What you’ll need
Nothing but an Apify account. You don’t need an API key, a proxy or a CAPTCHA solver. We keep a stored copy of the register, read with the Attorney General’s permission, and your run filters that copy, so it never waits on the registry. You pay $0.004 per registration returned, plus Apify’s own platform usage.
Step 1 — Open the actor
Open Pennsylvania Contractor Registration Lookup & Leads Scraper on the Apify Store and press Try for free. The actor’s own page on this site lists its live health and pricing.
Step 2 — Fill in the input
| Field | Type | Required | What it does |
|---|---|---|---|
mode | string | no | 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… (default auto) |
query | string | no | A business name (or listing number) to look up in the stored copy of the directory. Kept alongside ‘queries’ for saved tasks. |
queries | array | no | A business name (or listing number) to look up in the stored copy of the directory. Several values in one run; combined with ‘query’. (default []) |
status | string | no | Only listings in this status. Kept alongside ‘statuses’ for saved tasks. |
statuses | array | no | Only listings in this status. Several values in one run; combined with ‘status’. (default []) |
city | string | no | Only listings in this city. Kept alongside ‘cities’ for saved tasks. |
cities | array | no | Only listings in this city. Several values in one run; combined with ‘city’. (default []) |
categories | array | no | Only rows from this county (one or more). Available: adams, allegheny, armstrong, beaver, bedford, berks, blair, bradford, bucks, butler, cambria, cameron, carbon, centre,… (default []) |
maxResults | integer | no | Per query in lookup mode; per run in export mode. (default 500) |
Two fields decide what comes back. categories here means county, not trade: allegheny, philadelphia, montgomery and so on, plus out-of-state for firms based outside Pennsylvania. Leave it empty for the whole state. A county is the Attorney General’s own county search, and that search can list a contractor under more than one county (in the 2026-10-28 recon, Allegheny’s 4,145 included 71 out-of-state firms and 12 from Westmoreland); a run never returns the same registration twice. city matches the address on the registration, so city: Pittsburgh inside allegheny is the tight filter. The register publishes active registrations only, so status: Active changes little today; it drops the few whose expiry date has passed since the copy was read. To check registrations, put their numbers in queries; they look like PA178376.
A working input:
{
"categories": [
"allegheny"
],
"status": "Active",
"maxResults": 500
}
Step 3 — Run it
Press Start. A run reads the stored copy, so it finishes in seconds. The input above returns 500 of the 4,145 active Allegheny County registrations; raise maxResults to 4,145 to get them all. An empty result means no registration matches all your filters together, usually a city spelled differently from the registration’s address. You get a free no_match row saying so, and a registration number that isn’t on file gets one too. A county name the actor doesn’t know returns the full county list, also free.
Step 4 — Read the output
Each run returns a labelled table, not raw JSON:
Every row looks like this — a real row from a real run:
{
"listing_id": "PA178376",
"name": "Allens Handyman Services",
"phone": "412-447-1794",
"phone_e164": "+14124471794",
"street": "2283 Monroeville Rd",
"city": "Monroeville",
"region": "PA",
"postal_code": "15146",
"country": "US",
"category": "home improvement contractor",
"categories": [
"home improvement contractor"
],
"source": "snapshot",
"source_url": "https://hicsearch.attorneygeneral.gov/#PA178376",
"extras": {
"status": "Active",
"expires": "2027-04-16"
}
}
| Field | Example value | Meaning |
|---|---|---|
listing_id | PA178376 | Registration number (e.g. PA178376) |
name | Allens Handyman Services | Business name |
extras.status | — | Active; a registration whose expiry date has passed since the copy was read reads Expired, with a status_note |
extras.expires | — | Registration expiry date |
extras.types_of_work | — | Types of work the contractor declared (roofing, HVAC, electrical, …), where on file |
phone | 412-447-1794 | Phone as the registry printed it |
phone_e164 | +14124471794 | The same number in +1 format |
street | 2283 Monroeville Rd | Street address (address lines joined) |
Sellers come for phone and street; people checking a contractor come for listing_id and extras.expires. Every one of the 52,330 active registrations on 2026-10-28 has a phone and a street address. Watch three things. There is no email: the register doesn’t publish one. extras.types_of_work is on file for only about half the registrations, because the Attorney General added the field recently and older registrations predate it, so a trade filter on it misses contractors who never filled it in. And extras.expires is mostly within two years (99.7% by the end of 2028 on the 2026-10-28 data, a handful to 2030); one expiring next month is still active today.
Step 5 — Export it
Open the Dataset tab and export to CSV, JSON, or Excel — or pull the same rows from the API, which is what you want if this is going to run on a schedule.
What it costs
$0.004 per registration returned. Free rows (no match, unknown county, stale data) cost nothing. The Allegheny example above is 500 registrations, $2.00. All 4,145 active Allegheny registrations are $16.58; every active registration in the state, 52,330 rows, is about $209.32. Apify’s platform usage for a run this short is a fraction of a cent on top.
Where the data comes from
This reads the Pennsylvania Office of Attorney General’s Home Improvement Contractor search, with the Office’s permission, and returns only what that search shows a visitor. The registry is swept into a stored copy one county at a time over plain HTTP; runs filter the copy and call no source. Twice a day our checks run this actor against the stored copy and fail if active registrations in Pittsburgh stop returning 50 rows with registration numbers, phones and street addresses (what “verified” means).
Each county search passes a Cloudflare “verify you are human” check, which we solve once every couple of minutes of the sweep, not once per run of yours. The registry answers a whole county in one response, so the statewide sweep is 68 searches and a few minutes. It also has a quirk worth knowing if you build your own: when the human check expires, the search answers “0 results” instead of an error, which looks exactly like an empty county.
See it used
Allegheny County PA Home Improvement Contractors with Phone Numbers runs the input above for a supplier building a Pittsburgh-area call list.
If your contractors also work across the Delaware River, New Jersey Contractor License Lookup covers New Jersey’s home improvement contractors; for the New York side, New York Contractor License Lookup & Leads Scraper.
Last updated 2026-10-28