Building Permits Scraper: 12 Cities, Contractor Leads avatar

Building Permits Scraper: 12 Cities, Contractor Leads

Pricing

Pay per usage

Go to Apify Store
Building Permits Scraper: 12 Cities, Contractor Leads

Building Permits Scraper: 12 Cities, Contractor Leads

Building permits from 12 US and Canadian city open data portals in ONE schema: contractor name, phone, licence, owner, valuation and a derived project class (solar, roofing, HVAC, new build). Plus a contractor rollup. No API key, no proxy.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

The Mine Works

The Mine Works

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

2 days ago

Last modified

Share

๐Ÿ—๏ธ Building Permits Scraper: 12 Cities, Contractor Leads

โšก Public municipal open data over plain HTTP. No API key, no proxy, no browser, no login.

๐Ÿ’ธ You are charged only for a permit that actually lands in your dataset. Contractor rollup rows and the run summary are free.

Overview

Every US city publishes its building permits. Almost none of them publish it the same way. Chicago calls the scope of work work_description, Seattle calls it description, Philadelphia calls it approvedscopeofwork, and Boston writes the job value as the string "$45,000.00" while Austin writes it as a number spread across five different valuation columns depending on the trade. Chicago does not have a contractor column at all: it has contact_1_type through contact_15_type, and the contractor is whichever one has the word CONTRACTOR in its role.

That is the actual work, and it is what this actor does. It pulls permits from twelve municipal portals across three different open data platforms, normalises them into one row shape, and derives the two fields that make a permit useful commercially: a project class and a contractor identity.

โœ… 12 cities, one schema ยท โœ… Contractor name, phone and licence where published ยท โœ… Derived project class ยท โœ… Free contractor rollup ยท โœ… MCP ready for AI agents

Cities covered

KeyCityPlatformContractor published
new_york_nyNew York, NY (DOB NOW)Socrataโœ… business name + licence
chicago_ilChicago, ILSocrataโœ… name + trade + city
austin_txAustin, TXSocrataโœ… name + phone + trade
philadelphia_paPhiladelphia, PACartoโœ… name + city
new_orleans_laNew Orleans, LASocrataโœ… name + trade + licence
baton_rouge_laBaton Rouge, LASocrataโœ… name + owner
seattle_waSeattle, WASocrataโœ… when the city fills it
boston_maBoston, MACKANapplicant name only
san_francisco_caSan Francisco, CASocratanot published
cincinnati_ohCincinnati, OHSocratanot published
montgomery_county_mdMontgomery County, MD (residential)Socratanot published
edmonton_abEdmonton, ABSocratanot published

The last column is stated plainly rather than implied, because a permit dataset that does not name the contractor cannot be turned into a contractor lead by any amount of scraping. Set contractorLeadsOnly to true and the run keeps only rows that actually carry a company name.

The derived project class

A raw permit type reads EXPRESS PERMIT PROGRAM or OTC alterations permit. Nobody buys leads on that. The actor classifies every permit from its combined type, work class and description text into one of: solar, ev_charger, battery_storage, roofing, pool_spa, hvac, new_construction, adu, demolition, fence_deck, sign, electrical, plumbing, fire_safety, elevator, addition, remodel, other.

Order matters in the classifier. Solar is tested before electrical because a residential solar install is almost always filed as an electrical permit, and pools before plumbing for the same reason. So a search for solar returns the rooftop PV jobs rather than three thousand service upgrades.

Input

{
"cities": ["austin_tx", "chicago_il", "philadelphia_pa"],
"daysBack": 30,
"projectClasses": ["solar", "roofing"],
"minValuation": 5000,
"contractorLeadsOnly": true,
"contractorRollup": true,
"maxPermitsPerCity": 500
}
FieldWhat it takes
citiesOne or more city keys from the table above. Unknown keys are reported and skipped rather than silently ignored.
daysBackHow far back to look, counted from the permit issue date.
projectClassesKeep only these derived classes. Empty means every class.
keywordsKeep only permits whose type, work class, description or contractor name contains one of these words.
minValuationDrop permits below this declared job value. Permits with no published value are dropped when this is set.
contractorLeadsOnlyKeep only permits that name a contractor company.
contractorRollupAppend one aggregated row per contractor at the end of the run.
maxPermitsPerCityHard cap per city, and your main cost control.

Output

Every permit row:

{
"city": "austin_tx",
"city_name": "Austin, TX",
"state": "TX",
"permit_number": "2026-102151 EP",
"permit_type": "Electrical Permit",
"work_class": "Residential Repair",
"work_description": "Installation of a new residential roof-mounted solar photovoltaic system with battery energy storage...",
"project_class": "solar",
"status": "Active",
"issued_date": "2026-08-12T00:00:00",
"valuation_usd": 20000,
"address": "14016 TYBURN TRL",
"postal_code": "78717",
"latitude": 30.48875096,
"longitude": -97.79060856,
"contractor_name": "roofio LLC",
"contractor_phone": "5129223034",
"contractor_trade": "Electrical Contractor",
"has_contractor": true,
"source": "City of Austin issued construction permits"
}

And one free rollup row per contractor:

{
"_type": "contractor",
"contractor_name": "LS CONTRACTING GROUP, INC.",
"permit_count": 2,
"total_valuation_usd": 315840,
"avg_valuation_usd": 157920,
"cities": ["Chicago, IL"],
"trades": ["GENERAL CONTRACTOR", "MASON CONTRACTOR"],
"project_classes": ["addition", "remodel"],
"first_permit_date": "2026-07-28T00:00:00",
"last_permit_date": "2026-08-11T00:00:00"
}

Rollup grouping normalises legal suffixes, so ROOFIO LLC, Roofio, L.L.C. and roofio llc are one contractor while Roofio Austin stays separate.

Common use cases

Solar, roofing and HVAC lead generation. Filter to your class and city, get the job site address, the owner and the contractor who won the job. New permits appear within a day or two of issue.

Contractor prospecting. Sell to contractors instead of homeowners. The rollup ranks every contractor by total job value in the window, so your list is ordered by who is actually building.

Construction market intelligence. Count permits and dollars by class, city and month to size a market or track where activity is moving.

Supplier and manufacturer territory planning. Permit valuations and volumes are a leading indicator of material demand by postal code.

Property and investment research. New construction, ADU and demolition permits flag parcels in transition before they reach a listing site.

AI agents. Point an agent at it through MCP and ask which contractors pulled the most solar permits in Austin last month.

Cost control built in

Three guards run on every job, because a run that returns nothing should also cost nothing.

  1. Run level spend cap. A per city page cap is not a cost guard while the city count is unbounded, so the ceiling applies to the whole run rather than to any one city.
  2. Zero yield abort. Two consecutive pages that add nothing new and the city is dropped. One productive page resets the counter, so a healthy run never trips it.
  3. No transport escalation. A portal that does not answer is skipped and reported in the summary. There is no retry through a paid proxy or a browser, because escalating on a run that is already failing costs many times more and recovers almost nothing.

Pricing

EventPriceYou pay when
Permit delivered$0.001A permit row lands in your dataset

$1.00 per 1,000 permits. Contractor rollup rows, the summary row and empty runs are free.

Run it on a schedule

  1. Run once with the cities and classes you want, then click Save as a task.
  2. In the Apify Console go to Schedules โ†’ Create new.
  3. Pick a frequency and attach the saved task.
  4. Wire the dataset to Sheets, Slack, a CRM or a webhook from the Integrations tab.

Set daysBack to slightly more than your schedule interval and deduplicate on permit_number so a daily run never reprocesses the same permit.

FAQ

Is this data public? Yes. Every source is an official municipal open data portal, published by the city itself for reuse. No login, no key, no scraping of a protected site.

Why only twelve cities? Because these are the twelve that were probed live and returned real permit rows. Los Angeles requires a login, and San Diego, San Antonio, Denver, Nashville, Raleigh and Charlotte returned 404 or non JSON on their published endpoints. Dallas answers, but publishes its issue date as free text and has not been updated since 2019, so a date filtered query returns zero rows while looking healthy. A city listed here and returning nothing would cost you compute to discover, so those were left out rather than counted.

How fresh is it? Most portals publish daily. In testing, permits issued the previous day were already present in Austin, Chicago, Philadelphia, Boston and New Orleans.

Why is valuation missing on some rows? Because that city does not publish it, or published it as zero. It is left empty rather than guessed. Philadelphia and New Orleans zoning permits frequently have no value at all.

Can you add my city? If it has a public open data portal with a permits dataset, yes. Reach out through the Apify profile with the city and dataset link.

Use from Claude, ChatGPT and any MCP agent

https://mcp.apify.com/?tools=themineworks/building-permits-construction-leads

Or call it programmatically:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('themineworks/building-permits-construction-leads').call({
cities: ['austin_tx', 'chicago_il'],
daysBack: 30,
projectClasses: ['solar'],
contractorLeadsOnly: true,
maxPermitsPerCity: 500,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);

Questions, or need a city or field we do not return yet? Reach out through the Apify profile.