Building Permit Monitor - Construction Leads avatar

Building Permit Monitor - Construction Leads

Pricing

from $1.60 / 1,000 baseline permits

Go to Apify Store
Building Permit Monitor - Construction Leads

Building Permit Monitor - Construction Leads

Get alerted the moment a matching building permit is issued in Chicago, Austin, San Francisco, Seattle or NYC. Filter by keywords, permit type and valuation; alerts only on never-seen permits via official city open-data APIs. Project and contractor business data, no homeowner identities.

Pricing

from $1.60 / 1,000 baseline permits

Rating

0.0

(0)

Developer

Lowland Data

Lowland Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Building Permit Monitor — Construction Leads

Watches US city building-permit portals — Chicago, Austin, San Francisco, Seattle and New York City — for newly issued permits matching your profile: keywords, permit types, minimum project valuation. It alerts the moment a matching permit is issued, and it remembers what it has already told you — so an alert always means a genuinely new project, never a re-scrape of last week's list.

It reads each city's official open-data API (the access route the cities publish for exactly this), and the output is clean by construction: project, address, valuation and contractor business — never homeowner or applicant identities. Safe to store, share with your sales team, or import into a CRM.

Quick start (30 seconds, no account keys needed)

  1. Run the actor with the prefilled input (Seattle, last 14 days) — it finishes in a few seconds and shows a baseline of recent permits.
  2. Replace the demo profile with your own: your cities, your trade's keywords or permit types, your minimum project size.
  3. Add a daily schedule (Actor page → Schedule). From then on, runs are quiet until a permit you care about is actually issued.

Who uses this

  • A roofing contractor in Chicago monitors keywords: ["roof", "reroof"] daily. When a permit is issued two neighborhoods over, the alert lands with the address, valuationUsd and issuedDate — a lead the same week the project goes live, not after the crews are already on site.
  • A solar installer in Austin watches keywords: ["solar", "photovoltaic"] — every new solar permit shows which contractorName won the job, mapping both leads and competitors in one feed.
  • A building-materials supplier runs permitTypes: ["new construction"] with minValuationUsd: 500000 across all five cities — only projects big enough to supply, straight into the CRM via the API.
  • A market analyst tracks permit volumes and valuations per city as a construction-economy indicator — the baseline plus daily deltas is a clean time series.
  • An HVAC company pairs permitTypes: ["mechanical"] with alertOn: "new-and-updated" to also see status changes on projects it quoted.

What you get

One JSON item per event: a baseline snapshot per permit on the first run, then only transitions — newly issued matching permits, and (opt-in) status changes on permits you have already seen. A new-permit alert looks like:

{
"portal": "austin",
"permitNumber": "2026-091811 EP",
"permitType": "Electrical Permit",
"permitClass": "Residential",
"workClass": "New",
"description": "Install rooftop solar array, 24 panels, 9.6 kW",
"status": "Active",
"address": "8316 ROBERT ZION WAY",
"city": "Austin",
"state": "TX",
"zip": "78744",
"valuationUsd": 48500,
"appliedDate": "2026-07-16",
"issuedDate": "2026-07-17",
"expiresDate": "2027-01-13",
"contractorName": "Powerhouse Electrical Services, Inc.",
"contractorTrade": "Electrical Contractor",
"url": "https://abc.austintexas.gov/web/permit/public-search-other",
"isBaseline": false,
"isUpdate": false
}

Field honesty notes:

  • Every portal has its own vocabulary — permitType, permitClass and workClass carry the city's own labels, unnormalized, and every item's labels are reusable as your permitTypes filter.
  • valuationUsd appears only when the city publishes a project cost — many permits carry none, and absence does not mean small.
  • contractorName is the business on the permit where the city publishes one (Chicago and Austin do; San Francisco and Seattle mostly don't).
  • address is the project site the city publishes for public accountability. Who lives there is deliberately not in the data — see the compliance section.

How much does permit monitoring cost?

Pay per outcome — checking costs nothing; you pay when the monitor tells you something new:

  • New matching permit (the alert): $0.02 — also for status-change alerts.
  • Baseline item (first run only): $0.002.
  • Actor start: $0.005. Everything included - there are no separate platform-usage charges, and higher Apify plans get automatic unit-price discounts.

Cost feel: a focused trade profile in two cities sees a handful of new permits per working day → roughly $2–8/month for complete coverage, with the one-time baseline (say 100 permits) costing about $0.20. A run with zero new matches costs $0.005 all-in. Lead-gen services sell the same permit leads for $50–300/month.

Why an alert costs more than a scraped row

The store's headline ("from $2.00 / 1,000 baseline results") is what a trial costs: a 100-permit baseline ≈ $0.20. Apify displays every event per 1,000, so in the pricing table the alert event reads "$20.00 / 1,000" next to raw permit scrapers at $2–30 per 1,000 rows — but that comparison is between different things:

  • A raw permit scraper sells rows: to monitor with rows you re-download each city's recent permits every day and diff them yourself.
  • This monitor sells the transition: it remembers state in your account and charges $0.02 exactly when a permit you have not seen matches your profile. Nobody buys 1,000 alerts a month.

Not technical? Let your AI assistant set it up

Paste this prompt together with a line about your trade and cities:

Set up the Apify actor lowlanddata/building-permit-monitor to watch US building
permits for me. Build the input JSON from my business description:
- portals: which of ["chicago","austin","san-francisco","seattle","nyc"] I can serve
- keywords: 3-8 words my kind of project's description would contain
(e.g. ["roof","reroof"] for roofing, ["solar"] for solar installers)
- permitTypes: my trade's permit labels if I named any (e.g. ["electrical"])
- minValuationUsd: only if I gave a minimum project size
Leave alertOn, emitBaseline and maxItems at their defaults. Run it once and
show me the baseline as a table (description, address, city, valuation,
contractor, issued date). Then create a daily schedule for it and set up a
notification so I get an email when a run produces items.

Coverage — measured, not promised

  • Sources: each city's official open-data permit dataset (Socrata APIs) — Chicago, Austin, San Francisco, Seattle and New York City (DOB NOW). Keyless, sanctioned access; together several thousand permits/week unfiltered (measured July 2026, NYC alone ~3,000/week).
  • More cities are on the roadmap; each city ships only after its dataset passes the same field-level privacy verification.
  • Cities update their datasets on their own cadence — typically daily with a small lag after issuance. The monitor alerts when the dataset publishes the permit; that is as fast as official data allows.
  • Permit-type vocabularies differ per citypermitTypes is an honest substring match over each city's own labels, not a fake universal taxonomy.
  • A portal outage or dataset migration never fails the run: the affected city is reported in the run status (uncharged) and its permits alert once it recovers.

How the monitor thinks

  • First run = baseline. It fetches your profile's matches from the last lookbackDays (default 14), pushes them flagged isBaseline: true at the low baseline price, and memorizes them. emitBaseline: false seeds silently.
  • After that, only transitions. Each run queries from the previous run (one day of overlap; the memory absorbs duplicates) and pushes only permits it has never alerted. Status changes alert as isUpdate: true when alertOn: "new-and-updated"; with the default they stay silent but are remembered.
  • Editing the profile starts a fresh baseline — matching-relevant inputs are the profile's identity, so a changed profile can never flood you with alerts computed against another profile's memory. maxItems, emitBaseline and schedule changes do not restart anything.
  • Memory lives in a named key-value store (stateStoreName, default permit-monitor-state) in your account. Run several profiles on separate schedules against the same store — they never interfere. resetState: true wipes the current profile's memory.
  • A capped baseline samples every selected city round-robin instead of exhausting the first one.
  • Re-running after a crash or overlap re-alerts at most the last few unsaved items — state is checkpointed during the run.

Input reference

FieldTypeDefaultNotes
portalsarrayall fivechicago, austin, san-francisco, seattle, nyc.
keywordsarray[]OR, case-insensitive, over description + type labels.
permitTypesarray[]Substring match over the city's type/class labels only.
minValuationUsdintegerPermits without a published valuation always pass.
lookbackDaysinteger14Baseline reach on the first run (max 60).
alertOnstring"new""new-and-updated" adds status-change alerts.
emitBaselinebooleantruefalse = silent seeding.
resetStatebooleanfalseOne-shot: wipe this profile's memory.
maxItemsinteger500Overflow alerts on the next run, never lost.
stateStoreNamestring"permit-monitor-state"Only change to fully isolate monitor instances.

Scheduling and API use

Run it daily and pair the schedule with a Monitoring alert on "dataset items

0" to get an email exactly when a permit matched.

curl -X POST \
"https://api.apify.com/v2/acts/lowlanddata~building-permit-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"portals": ["austin"], "keywords": ["solar"]}'
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('lowlanddata/building-permit-monitor').call({
portals: ['chicago', 'austin'],
keywords: ['roof', 'reroof'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items); // only new permits since the last run
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("lowlanddata/building-permit-monitor").call(run_input={
"portals": ["seattle"],
"permitTypes": ["mechanical"],
})
items = client.dataset(run["defaultDatasetId"]).list_items().items

Daily runs finish in well under a minute, comfortably inside the 300s sync-call wall; a 60-day four-city baseline is the one case that can take a couple of minutes — run it async once.

Use it with AI agents (MCP)

Claude, Cursor and other MCP-capable agents can run this monitor as a tool through Apify's hosted MCP server: the agent builds the profile from a plain-language brief, runs it, and reads the leads back — no glue code.

Claude Code:

$claude mcp add apify --transport http "https://mcp.apify.com?actors=lowlanddata/building-permit-monitor"

Cursor or Claude Desktop (add a custom connector / MCP server with this URL):

https://mcp.apify.com?actors=lowlanddata/building-permit-monitor

Sign in with your Apify account when prompted — runs are billed to it. Setup details per client: Apify MCP docs, or start from this actor's own MCP page: apify.com/lowlanddata/building-permit-monitor/api/mcp.

Prompts that work once connected:

  • "Watch Chicago and Austin for new roofing permits and show me today's leads."
  • "What solar permits over $30k were issued in Austin this week, and which contractors got them?"
  • "Track new-construction permits over $1M across all five cities and alert me daily."

Yes — building permits are public records, and these five cities publish them through official open-data APIs exactly so they can be reused. No login walls, no anti-bot bypass — if a portal is unreachable, the run reports it rather than fighting it. And the output is safe by construction: homeowner and applicant identities are never extracted (see below).

How you contact a lead is on you — this tool gives you the public project record, not marketing-law advice for your state.

Compliance: safe to store, safe to CRM-import

Permit records as published by the cities can contain personal data — owner and applicant names, phone numbers, even the applicant's own address. This actor's guarantee is structural: each city's mapper whitelists fields IN (project, address, valuation, dates, contractor business name/trade), so person-fields never map at all, and an automated test proves it against each city's real column names. The project address stays — it is the publicly published project site and the lead's core value; who lives there is deliberately unreachable. One honest limit: description is text the applicant wrote — on rare occasions someone writes a name into free text. We cannot rewrite public records; everything structured is guaranteed clean.

FAQ

Why did my second run return nothing? Because nothing new matched — that is the product working. The first run is the baseline; afterwards you only pay for permits you have not seen. The run status says how many permits were checked.

Does the output contain homeowner names or phone numbers? No — by design. The city mappers never touch owner/applicant fields. You get the project, the address, the valuation and the contractor business.

Which cities are covered? Chicago, Austin, San Francisco, Seattle and New York City — cities with official open-data permit APIs. More follow (New York next); tell us which city you need via the Issues tab.

Can I monitor several trades or cities separately? Yes — one schedule per profile, same actor, same state store. Each profile keeps its own memory.

How fresh are the alerts? As fresh as the city's dataset — typically the permit appears within a day or two of issuance. The monitor can't be faster than the official data, and doesn't pretend to be.

What does a status-change alert mean? With alertOn: "new-and-updated", a seen permit whose status moves (e.g. Active → Finaled) alerts once as isUpdate: true — useful for tracking projects you quoted.

Troubleshooting

  • "Every portal failed" — the open-data platforms are down or unreachable; re-run later. Single-portal failures never fail the run.
  • " — the lookback window matched more than ~10,000 permits; lower lookbackDays or narrow the profile.
  • "Monitor state record … is not readable" — the state was written by a newer version; run once with resetState: true to start a fresh baseline.

Something else off? Open an issue on the actor's Issues tab — reports are read and answered.