---
title: "Public records API"
description: "A public records API for building permits, code violations, business registries, license boards and property records from city, county and state portals."
canonical: https://www.zenrows.com/use-cases/public-records-api
updated: 2026-10-18
---

# Public records API

A public records API retrieves records that government agencies publish online, such as building permits, code violations, business filings, professional licenses and property assessments. Zenrows fetches those agency portals, including ones behind Akamai or Imperva, runs their search forms in a Browser session when needed, and returns each record as JSON your application stores and queries.

## Sample output: one building permit

```json
{
  "jurisdiction": "New York, NY",
  "record_type": "building_permit",
  "permit_number": "B00123456-I1",
  "job_type": "Alteration type 2",
  "status": "issued",
  "address": "350 Example Ave, Brooklyn, NY 11215",
  "block_lot": "01234 / 0056",
  "filed_date": "2026-08-14",
  "issued_date": "2026-09-30",
  "expiration_date": "2027-09-30",
  "work_description": "Interior renovation, new partitions, plumbing",
  "estimated_cost": 185000,
  "applicant": { "role": "Registered architect", "license_type": "RA" },
  "open_violations": [
    { "id": "V-0921-26", "issued": "2026-09-21", "type": "Work without permit", "status": "resolved" }
  ],
  "record_url": "https://permits.example.gov/record/B00123456",
  "fetched_at": "2026-10-17T06:00:12Z"
}
```

An illustrative permit record as JSON; keep `record_url` and `fetched_at` on every record so a reviewer can trace each field back to the agency page.

## What public records teams collect

Agencies publish records in thousands of separate systems and few of them offer an API, so most projects pick one record type and work through the jurisdictions they need. Court dockets and attorney records follow the same pattern, with search forms that need more steps.

| Job | Data you collect | Typical sources |
|---|---|---|
| Permit tracking | Permit number, job type, status, filing and issue dates, work description | NYC Department of Buildings, LADBS, city and county permit portals |
| Violation and complaint alerts | Violation ID, issue date, penalty, hearing date, 311 complaint status | Building department violation systems, city 311 portals |
| Business entity lookups | Legal name, entity number, status, registered agent, officers, filing history | Florida Sunbiz, state secretary of state sites, UK Companies House, Quebec enterprise register |
| License verification | License number, holder, status, expiry, disciplinary actions | State contractor, nursing, real estate and insurance licensing boards |
| Property and tax records | Owner of record, parcel ID, assessed value, sale history, tax status | County assessor, recorder and treasurer portals |
| Unclaimed property searches | Holder, owner name match, amount band, claim status | MissingMoney.com, state treasury unclaimed property sites |
| Due diligence reports | Registrations, liens, insolvency notices, ownership links | Company registries, UCC filing searches, official gazettes |

## Why government portals block automated lookups

Most government sites were built for one resident looking up one address. Automated lookups run into protections and page designs that assume a person at a keyboard.

| What breaks | What it looks like | How Zenrows handles it |
|---|---|---|
| Bot protection on the portal | Akamai or Imperva returns a 403, or a challenge page that never resolves, after a few lookups | Zenrows' stealth gets through Akamai, Imperva and similar protections, with retries on failed attempts |
| Challenge pages before the portal | A Turnstile or reCAPTCHA challenge page appears before the search page loads | Adaptive Stealth Mode escalates to browser rendering and stealth when the challenge page appears, and gets through it |
| Multi-step search forms | Results appear only after picking a borough, typing a house number and submitting, with cookies carried between steps | Browser runs the whole form in Playwright or Puppeteer inside one session |
| Legacy HTML | Tables nested in tables with no classes or IDs, field labels in the same cell as values | Extract returns the fields as JSON without selectors to maintain |
| One system per jurisdiction | Every county names its fields differently and counties can change software vendors between years | Extract maps each page to JSON, so you normalize once instead of writing a parser per county |
| Status changes overnight | A weekly sweep misses the stop-work order issued on Tuesday | Batch re-checks your whole record list on the schedule you set |

## How to build a public records API

1. **Check for an open data feed first.** Many cities publish permits and complaints on an open data portal. Use the feed where it exists and scrape only the records or fields it leaves out.
2. **Fetch records that have their own URL.** Send the record page to Fetch with Adaptive Stealth Mode, and name the fields with CSS selectors or let Extract find them.
3. **Drive search forms with Browser.** When a record only appears after a search, connect Playwright to Browser, fill the form, submit it and read the result table.
4. **Re-check on a schedule.** Store every record URL you find, resubmit the list with Batch, and alert your users when a status, date or penalty changes.

```python Python
# Search a permit portal by address with Playwright over Zenrows Browser
# pip install playwright
from playwright.sync_api import sync_playwright

WS = "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY"

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(WS)
    page = browser.new_page()
    page.goto("SEARCH_URL")  # the portal's property search form
    # Example selectors: match them to the portal's form
    page.select_option("#borough", "3")
    page.fill("#house-number", "350")
    page.fill("#street", "Example Ave")
    page.click("button[type=submit]")
    page.wait_for_selector("table.results")
    rows = page.locator("table.results tr").all_inner_texts()
    print(rows)
    browser.close()
```

```bash cURL
curl -G "https://api.zenrows.com/v1/" \
  --data-urlencode "apikey=YOUR_ZENROWS_API_KEY" \
  --data-urlencode "url=RECORD_URL" \
  --data-urlencode "mode=auto" \
  --data-urlencode 'css_extractor={"permit":"td.permit-no","status":"td.status","filed":"td.filed","work":"td.work-desc"}'
```

```bash CLI
npx -y @zenrows/cli init
zenrows extract RECORD_URL --autoparse
zenrows extract RECORD_URL --css '{"permit":"td.permit-no","status":"td.status","filed":"td.filed"}'
```

```text Claude Code (MCP)
# Connect once
claude mcp add zenrows -e ZENROWS_API_KEY=YOUR_ZENROWS_API_KEY -- npx -y @zenrows/mcp

# Then ask:
Use Zenrows to open the NYC Department of Buildings permit search, look up 350 Example Ave in Brooklyn, and return each permit's number, job type, status, filed date and issued date as JSON with the record URL.
```

## Which Zenrows product fits

| If you need to | Use | Why |
|---|---|---|
| Look up a record that has its own URL | [Fetch](/products/fetch) | One request with Adaptive Stealth Mode returns the page, from portals behind Akamai or Imperva included |
| Read records from county pages that all look different | [Extract](/products/extract) | Automatic structured extraction returns JSON from each page without per-site selectors |
| Search by name, address or parcel through a form | [Browser](/products/browser) | Playwright or Puppeteer runs the form in a real browser session, step by step |
| Re-check thousands of permits or licenses every night | [Batch](/products/batch) | The whole record list runs as one job, with results by webhook or download |

## What success looks like

| Measure | What good looks like | How to check it |
|---|---|---|
| Lookup completion | Every query ends in a parsed record or a confirmed no-match page, never a blank | Log the outcome of each lookup and review failures per jurisdiction weekly |
| Time to change | A new violation or status change reaches your users within one polling interval | Compare the agency's posted date with the time your system first stored it |
| Jurisdiction coverage | Every city or county you committed to returns parsed records | Count parsed records per jurisdiction against your coverage list |
| Traceability | Every field carries its source URL and fetch time | Spot-check a random sample of records against the live agency page |
| Retries per lookup | Retries per completed lookup stay flat as you add jurisdictions | Divide retried requests per job by completed lookups, per jurisdiction |

## Public records API FAQ

### Is there one API for all public records?

No single agency publishes one. Records stay with the body that issues them: building departments, secretaries of state, county assessors, licensing boards. Some publish open data feeds, and most offer only a search page. Zenrows puts one API in front of those pages. You send a record URL or drive the search form, you get the page or its fields back as JSON, and you keep the records in your own database.

### Do I get a records database or live lookups?

Live lookups. Zenrows fetches the agency pages you point it at, at the moment you ask, so each record is as current as the source and carries the URL it came from. For a history of a permit or a company's filings, store each response with its fetch time and compare runs over time.

### How do I get building permit data?

Start with the city's open data portal, since many large US cities publish permit filings there. For fields the feed omits, or for cities with no feed, fetch the permit detail pages from the building department's site. Where permits are only reachable through an address or BIN search, run the search in a Browser session and save the record URLs it returns for later refreshes.

### Can I use this as a background check API?

Zenrows fetches the court, license and registry pages a background check draws on, and your service assembles the report. In the US, a report used for employment, tenancy, credit or insurance decisions falls under the Fair Credit Reporting Act, which places accuracy and dispute duties on whoever compiles it. Ask your legal team before you build one.

### Is it legal to scrape public records?

Public records are published so people can read them, and collecting them is common in compliance, due diligence and construction. Many records still contain personal data, so privacy law applies to how you store and use them, and each portal has its own terms of use. Collect only the fields your purpose needs, and ask your legal team about your specific use.

### How do I get business registry data?

Most secretary of state sites, Florida Sunbiz and UK Companies House give each entity a detail page with its own URL. Search once by name or entity number, save the detail URLs, and fetch those pages directly afterwards. Extract returns the legal name, status, registered agent, officers and filing history as JSON, so the same pipeline works across states whose pages look nothing alike.

### Can Zenrows reach portals behind Akamai or Imperva?

Yes. City permit systems, state registries and unclaimed property sites often sit behind Akamai, Imperva or a Turnstile challenge page. Fetch requests in Adaptive Stealth Mode, or lookups run through Browser, get through those protections with Zenrows' stealth and retries.

### How do I keep property and license records current?

Store each record URL after the first lookup. Resubmit the list to Batch on a schedule, daily for open permits and violations, monthly for assessments and licenses, and compare each run with the last to catch status changes, new penalties and expirations.

## Related use cases

- [Real estate web scraping](https://www.zenrows.com/use-cases/real-estate-web-scraping.md): Listings, prices, agents and price history from property portals, kept current at portfolio scale.
- [Firmographic data](https://www.zenrows.com/use-cases/firmographic-data.md): Industry, headcount, HQ, locations and funding per company, built from registries, company sites and directories.

Start free: https://app.zenrows.com/register. Docs: https://docs.zenrows.com
