Building Permit Scraper: Contractor Leads & Status (Accela)
Pricing
from $6.00 / 1,000 permit records
Building Permit Scraper: Contractor Leads & Status (Accela)
Building permit data and leads from any Accela Citizen Access city or county: permit type, status, address, valuation and contractor details, with status change alerts.
Pricing
from $6.00 / 1,000 permit records
Rating
0.0
(0)
Developer
Final Dynamics
Maintained by CommunityActor stats
0
Bookmarked
5
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
Pull building permit data and building permit leads from any city or county that runs Accela Citizen Access, the permitting portal used by hundreds of US jurisdictions. Give it an agency and a date range; get one clean row per permit with the type, status, address, valuation and, if you want them, the contractor's name, licence and phone from the permit page.
Built for people who buy permit data on a schedule: roofing, solar, HVAC and remodeling contractors looking for fresh projects, lead sellers, suppliers watching new construction, and anyone who needs a weekly feed instead of a one-off export.
What you get
One row per permit. Every field is flat, so the dataset drops straight into Google Sheets, Airtable or a CRM.
| Field | Example |
|---|---|
permitNumber | BP-202603758 |
permitType | Commercial - Fire Sprinkler |
permitTypeNormalized | roofing, solar, hvac, new_construction, electrical, plumbing, demolition or other |
status | Routed for Review |
appliedDate | 2026-09-15 |
addressFull, street, addressCity, addressState, zip | 140 CHASTAIN PARK AVE NW, ATLANTA GA 30339 |
description | Project description as published |
declaredValuationUsd | 48500 (from the permit page, when published) |
contractorName, contractorLicense, contractorPhone | From the permit page with includeDetails |
parcelId, issuedDate, finalDate | From the permit page, when published |
detailUrl | Direct link to the public permit record |
agencyKey, agencyName, city, state, module | Which portal the row came from |
isNewSinceLastRun, previousStatus, statusChanged | Set by the incremental and monitoring modes |
scrapedAt | UTC timestamp |
Anything the portal does not publish is null. Owner names are off by default because owners are
usually private individuals; turn on includeOwnerName together with includeDetails if you need
them and your use allows it.
How to use it
Pick an agency, choose how far back to look, run.
{"agency": "ATLANTA_GA","module": "Building","lastDays": 7,"maxResults": 200}
lastDays is prefilled at 7: the last 7 calendar days ending today (UTC), including today, so a
saved task stays current. For a fixed window, set lastDays to 0 and give dateFrom and
dateTo; both dates are inclusive. A run that gives neither also searches the last 7 days.
maxResults caps the rows you pay for.
permitTypeContains keeps only rows whose record type contains one of your words. To choose types
yourself instead, set recordTypes to names from the selected module's Record Type menu, for
example ["Commercial Demolition"] in Atlanta. Up to 25 types are accepted. A name the portal does
not offer stops the run and lists examples of what it does offer; if the portal publishes no menu,
clear recordTypes and the actor searches every type. permitTypeContains still filters the rows
when recordTypes is set, and leaving both empty returns every type. A narrow filter can still take
a while on portals where the actor has to read the whole date window.
maxPages (60 by default) is the most result pages one run reads, counting every record type it
searches, even types with no matches. A run that reaches it stops and tells you to raise maxPages,
shorten the date window or pick fewer recordTypes. What you keep from a run that stops early
depends on the mode:
- A run without
includeDetailsor a status monitor saves rows page by page, so you keep every finished page even if a later page fails or the run is aborted. - With
includeDetails, rows are saved at the end, once the actor has checked that enough permit pages loaded (see Good to know). A details run that is aborted or times out before then saves no rows, so give it enough time. - A status monitor saves all or nothing: an incomplete run delivers no rows and leaves the saved
statuses as they were.
statusMonitorMaxPagesalso limits monitors, and the lower of it andmaxPagesapplies. - An incomplete run never moves the
sinceLastRunmarker forward, so nothing is skipped; rows it did deliver may come again, and be charged again, on the next run.
Recent permits on a schedule: set lastDays: 14 to search the last 14 calendar days ending
today (UTC), including today. The date window moves automatically on every run, overriding
dateFrom and dateTo, so saved tasks stay current. Set lastDays to 0 to use fixed dates.
Roofing and solar jobs worth $10,000 or more, with contractor details:
{"agency": "SACRAMENTO","module": "Building","dateFrom": "2026-09-01","dateTo": "2026-09-15","permitTypeContains": ["roof", "solar"],"minValuation": 10000,"includeDetails": true}
permitTypeContains matches any of the words against the permit type. Valuation and contractor
fields live on the permit page, so includeDetails must be on for minValuation to apply.
Only permits you have not seen before: create a key-value store in Apify Console, select it as
stateStoreId, set sinceLastRun: true, and reuse the same run configuration (a saved task). Each
complete run remembers the newest applied date it delivered and the next run emits only permits with
a newer applied date. It also remembers which permits it delivered, so a permit the portal
republishes without an applied date is not sent to you twice. A run that hits a cap or an error
never moves that marker, so nothing is skipped.
Status changes on a schedule: create a key-value store in Apify Console, select it as
statusStateStoreId, and schedule the task. The first complete run stores every permit's status;
later runs emit only permits whose status changed, with previousStatus filled in. Give each
schedule its own store. Set maxResults above the number of permits in your window (for example
5000): a monitor that reaches maxResults saves nothing and the run fails, so the schedule shows
it. A week of Atlanta building permits is already more than the default 200.
Other record types: set module to Enforcement for code enforcement cases or Planning for
planning and zoning cases. The same fields are used; permitNumber holds the case number.
Any other Accela portal on aca-prod.accela.com: set agency to the portal's agency code and
portalUrl to its base URL, for example https://aca-prod.accela.com/CITYNAME/. Portals a city
hosts on its own domain are not supported yet. Portals must offer a public General Search with a
date range; a handful do not, and the run reports that clearly.
Quick start
No code needed; about two minutes from input to spreadsheet. For a longer walkthrough with example output, read the guide Build a permit research spreadsheet.
- Open the example "Sacramento roofing and solar permits, last 14 days" on this actor's page, or paste one of the inputs above into the input form.
- Click Start and wait until the run shows Succeeded.
- Open the Output tab to see the permits as a table.
- Click Export, choose CSV or Excel, and download the file.
- In Google Sheets, choose File > Import > Upload and select the file.
Automate it
A weekly permit pull: keep lastDays in the task (for example 7 or 14) so the date window moves
with every run. Click Save as a new task, then add a schedule for that task under Schedules
in Apify Console (for example every Monday at 07:00). To get only permits you have not received
before, turn on sinceLastRun and select a key-value store as stateStoreId; for status changes
use statusStateStoreId as described above.
Into Google Sheets with n8n: add the Apify node with the On new Apify Event trigger,
choose your task and the succeeded event, then add an Apify node with Get Dataset Items
using the run's defaultDatasetId, and finish with a Google Sheets node that appends the rows.
Every scheduled run then adds its permits to the same sheet. Make and Zapier have Apify integrations
that work the same way.
Pricing
Pay per event. There are no platform usage charges on top.
| Event | Price | When |
|---|---|---|
actor-start | $0.01 | Once per run |
permit-record | $0.006 | Per permit row delivered |
contractor-detail-page | $0.004 | Per permit page fetched, only when includeDetails is on |
Examples: 200 permits without details cost $1.21. Twenty permits with contractor details cost $0.21. A weekly pull of 500 permits with details is about $5.01. A run that finds nothing costs $0.01.
Set a maximum charge on the run if you want a hard ceiling; the actor stops cleanly at the cap.
Coverage
These portals are bundled and verified. Any other portal hosted on aca-prod.accela.com works with
portalUrl; portals a city hosts on its own domain are not supported yet.
| Agency key | Jurisdiction | State |
|---|---|---|
ANAHEIM | Anaheim | CA |
ATLANTA_GA | Atlanta | GA |
BREA | Brea | CA |
CRYSTALLAKE | Crystal Lake | IL |
CULVERCITY | Culver City | CA |
DELAND | DeLand | FL |
DOWNEY | Downey | CA |
FONTANA | Fontana | CA |
GOODYEAR | Goodyear | AZ |
HARTFORD | Hartford | CT |
HCFL | Hillsborough County | FL |
HERMOSABEACH | Hermosa Beach | CA |
LOVELAND | Loveland | CO |
MANTECA | Manteca | CA |
MILWAUKEE | Milwaukee | WI |
MISSOULA | Missoula | MT |
MONTEREYPARK | Monterey Park | CA |
PALOALTO | Palo Alto | CA |
RENO | Reno | NV |
SACRAMENTO | Sacramento | CA |
STOCKTON | Stockton | CA |
TAMPA | Tampa | FL |
THURSTONCO | Thurston County | WA |
VISALIA | Visalia | CA |
Some fields vary by agency: valuation comes from each permit's own page, so it needs
includeDetails and some portals do not publish it, and a few portals list the address in a
different column. Rows always carry what the portal shows.
Good to know
- If a permit's own record page still fails to load after retries, the permit is delivered from the
search results with
detailFetchFailed: true, the fields that come only from that page empty, and nocontractor-detail-pagecharge. If more than 25 record pages, or more than 20% of those tried, fail, the run stops and saves no rows. - Public records only. If a portal blocks access, the run fails with a clear message rather than returning an empty result.
- Dates are the portal's local calendar dates. A permit's applied date is what the agency shows in its search results; on some portals that is the last update date.
- The actor is not affiliated with Accela or with any listed agency. Public records can be incomplete or corrected later.
FAQ
How do I get a list of new building permits for my city?
If your city or county runs Accela Citizen Access (the site address usually contains
aca-prod.accela.com), enter its agency key and a date range or lastDays. The Coverage table
lists the portals we have verified.
Can I get roofing, solar or HVAC permit leads only?
Yes. Put words from the permit type in permitTypeContains, for example ["roof", "solar"], and
turn on includeDetails for the contractor's name, licence and phone.
Can I look up a permit by address?
The actor searches by date, not by address. Pull the date range you need and filter the address column in your spreadsheet.
Is building permit data public?
Yes. Permits are public records, and the actor reads the same public pages anyone can open on the city's portal.
Related actors
- Code Violation Leads: code enforcement cases from Accela cities and counties.
- New Homeowner & Business Leads (Certificates of Occupancy): newly finished homes and business premises.
- Florida Lis Pendens, Pre-Foreclosure & Deed Leads: lis pendens and deeds from six Florida county clerks.
Questions or a portal that misbehaves? Open an issue on the actor page and include the agency and date range.