# Building Permits & Contractor License Intel (`seibs.co/building-permits-intel`) Actor

Unifies US building-permit + contractor-license portals into one schema: type, valuation, status, contractor, address. Value layer: contractor linking, property clustering, and scored high-intent trade signals (solar/roofing/HVAC) that cross-sell into trades lead-gen. Logged-out public records.

- **URL**: https://apify.com/seibs.co/building-permits-intel.md
- **Developed by:** [Seibs.co](https://apify.com/seibs.co) (community)
- **Categories:** Business, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 permit records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Building Permits & Contractor License Intel

> **TL;DR for contractors, suppliers, solar/roofing/HVAC sales teams, insurers, and real-estate investors:** Aggregates building-permit and contractor-license data from the fragmented US city/county/state government portals (public-by-law) into **one normalized schema** - permit type, declared valuation, status, contractor, address, dates, parcel - then adds the value layer that makes a permit sellable. A fresh permit is the highest-intent buying signal in the trades: a commercial HVAC permit is a service-contract lead, a roofing permit is a supplier's prospect, a solar/pool permit is an installer's lead. On top of the raw records it runs **contractor linking** (a contractor's permit footprint across jurisdictions + license), **property clustering** (multiple permits at one address = an active, capital-committed project), and **scored trade signals** (a 0-100 buying-intent score per permit with a cross-sell hint into the trades lead-finders). The underlying data is public, but it lives behind 50+ separate permit portals with no unified free API - the exact fragmentation Shovels (quote-gated) and BuildZoom monetize. Government public records, logged-out, PII-minimized. Free Apify plan covers exploration runs on your $5 platform credit.

### Run it in 30 seconds

```python
## Via the Apify Python SDK
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("seibs.co/building-permits-intel").call(run_input={
    "mode": "trade_signals",
    "jurisdictions": ["CHICAGO", "AUSTIN", "NYC", "SF", "SEATTLE"],
    "trades": ["solar", "roofing", "hvac", "pool"],
    "max_results_per_jurisdiction": 25
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
````

Or via curl:

```bash
curl -X POST "https://api.apify.com/v2/acts/seibs.co~building-permits-intel/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode": "permit_search", "jurisdictions": ["CHICAGO","AUSTIN","SF"], "query": "roof"}'
```

Or click "Try for free" on this page if you prefer the no-code UI.

### What you get

Each run produces:

- A clean dataset, filterable in the Apify console and downloadable as CSV or JSON
- An OUTPUT.html dashboard preview of your top records
- A sample-output preview at [`.actor/sample-output.json`](./.actor/sample-output.json)
- An `access_notes` record up top documenting each jurisdiction's portal, platform, access method, proxy needs, any cost/bulk path, and a responsible-use note

### What does Building Permits Intel do?

It queries each selected jurisdiction's public permit feed and normalizes every result into one schema: `permit_number`, `jurisdiction`, `permit_type` (normalized to `new_construction` / `addition_alteration` / `roofing` / `solar` / `electrical` / `plumbing` / `mechanical` / `pool` / `demolition` / ...), `trade` (the contractor specialty the permit signals demand for), `status` (normalized to `issued` / `final` / `pending` / `expired` / ...), `valuation_usd` + `valuation_band`, `work_description`, `address`/`city`/`state`/`zip`/`parcel_id`, `contractors` (name + trade + license + city), `owner_name` where public, and `source_url`. Then it runs the **value layer**:

- **Trade classification** - every permit is tagged with the trade it signals demand for (solar/roofing/HVAC/pool/electrical/plumbing/...) off the union of permit type, work description, and the contractor's declared trade, plus an intent priority.
- **Contractor linking** - groups permits by a normalized contractor key into a footprint per contractor: permit count, trades worked, jurisdictions, total + median declared valuation, and license number/type where the permit exposes it. Contractors on many permits or multiple jurisdictions are flagged `high_volume` (the supplier/recruiter target).
- **Property clustering** - multiple permits at one address (a remodel + electrical + plumbing, or a new-build sequence) cluster into a `permit_cluster` rollup - a richer, capital-committed lead than a single permit.
- **Trade-signal scoring** - scores each permit into a 0-100 buying-intent signal (trade priority x declared valuation x recency) and tags the portfolio lead-finder it cross-sells into.

### Modes

| Mode | What it returns |
|---|---|
| `permit_search` (default) | Normalized permits per jurisdiction (optional `query`, `address`, `date_from`/`date_to`, `min_value`, `trades` filters), plus contractor links and property clusters. |
| `permit_profile` | Direct lookup for specific permits by `permit_numbers=[{jurisdiction, permit_number}]`. |
| `contractor_lookup` | One `contractor` footprint record per requested contractor: permit count, trades, jurisdictions, total valuation, license. Charges `contractor_enrichment` per footprint. |
| `trade_signals` | Permits scored into ranked, cross-sell-ready `trade_signal` records (intent score + cross-sell hint). Charges `valuation_signal` per signal. |

### Coverage: verified metros + a national catalog

Permit portals share no schema and no unified API - that fragmentation is the moat we unify. Coverage is honestly tiered:

**Fully-parsed feeds (5)** - request + parser verified against the live portal with captured fixtures, returns real normalized permit data. All five are genuinely open Socrata open-data feeds (logged-out, no anti-bot, JSON):

| Jurisdiction | Feed | Valuation | Contractor | Notes |
|---|---|---|---|---|
| **CHICAGO** | City of Chicago Building Permits | `reported_cost` | Yes (contractor contacts) | Rich: work description + multiple contractor contacts per permit. |
| **AUSTIN** | City of Austin Issued Construction Permits | `total_job_valuation` | Yes (`contractor_company_name` + trade) | Permit class / work class + contractor trade. |
| **NYC** | NYC DOB Permit Issuance | (not in feed) | Yes (permittee + **license #/type**) | The contractor-license linkage layer; owner business name. |
| **SF** | San Francisco DBI Building Permits | `estimated_cost` / `revised_cost` | (not in feed) | Strong valuation + existing/proposed use & units. |
| **SEATTLE** | Seattle Built Environment Permits | `estprojectcost` | (when present) | Permit class/type + work description. |

**Catalog-registered (28)** - correct portal + platform (Socrata / ArcGIS / Accela / EnerGov / eTRAKiT / license board) + access method + anti-bot level recorded, anti-bot escalation pipeline wired, jurisdiction-specific parser pending (these return a documented `jurisdiction_pending` note). Includes major metros/counties - **Los Angeles, Houston, Phoenix, San Antonio, San Diego, Dallas, San Jose, Denver, Portland, Las Vegas/Clark County, Miami-Dade, Nashville, Charlotte, Columbus, Fort Worth, Boston, Washington DC, Philadelphia, Atlanta** - and state contractor-license boards - **CSLB (CA), FL DBPR, AZ ROC, OR CCB, NV NSCB, UT DOPL, WA L\&I, NC LBGC**.

The Socrata metros in the catalog (LA, Dallas, Denver, Nashville, Boston, DC, Philadelphia, San Diego) phase in with a single field map - the `SocrataPermitConnector` base does the request building. The live `access_matrix` (all jurisdictions, with `coverage` and `platform` per entry) is emitted in the `access_notes` record on every run. Pass `jurisdictions: ["ALL"]` to query every jurisdiction, or `["FULL"]` for the verified metros.

### Anti-bot escalation (residential + browser)

The open Socrata feeds clear on a plain request. The catalog Accela/EnerGov metros and some license boards sit behind a JS/SPA layer or an edge WAF. The client escalates automatically instead of giving up:

1. **httpx** over the DATACENTER proxy - cheapest, tried first; clears the open feeds outright.
2. **curl\_cffi** with real Chrome TLS impersonation over the RESIDENTIAL proxy - defeats JA3/TLS-fingerprint WAFs.
3. **Playwright/patchright** headful browser over the RESIDENTIAL proxy - for true JS/SPA/CAPTCHA portals. Set `browser_cdp_url` (or `BROWSER_CDP_URL`) to a warm anti-detect browser to pass Cloudflare-managed challenges; the per-search-CAPTCHA portals additionally need the opt-in solver (`CAPTCHA_SOLVER_PROVIDER` + `CAPTCHA_SOLVER_KEY`).
4. **Fail-soft** - if every tier is blocked/unavailable, the connector emits a documented `fetch_error` and the run still finishes SUCCEEDED.

### Pricing (pay-per-event)

| Event | Price | When |
|---|---|---|
| `permit_record` | $0.005 | Per normalized permit. |
| `valuation_signal` | $0.008 | Per permit carrying a parsed dollar valuation, and per scored `trade_signal` in trade\_signals mode. |
| `contractor_enrichment` | $0.010 | Per contractor footprint resolved in contractor\_lookup mode. |
| `scheduled_delta_run` | $0.050 | Per scheduled monitor-mode run that emits a change digest. |

A run that returns nothing costs nothing. This undercuts the quote-gated incumbents (Shovels, BuildZoom) and turns a per-record pay-as-you-go price on data their sales teams gate behind annual contracts.

### Monitor mode (new-permits watchlist)

Run this actor on an Apify **Schedule** and it switches to monitor mode: it diffs this run's permits against the last scheduled run and emits a change digest (new permits, status changes, finals) - the "alert me to new roofing permits in my metro" watchlist. Set `monitor_webhook_url` to post the digest to Slack. Charges one `scheduled_delta_run` per scheduled run.

### Cross-sell into the trades lead-finders

Every `trade_signal` carries a `cross_sell_actor` hint pointing at the portfolio lead-finder that consumes it (a roofing/HVAC/solar permit feeds **home-services**; a new-build/demo permit feeds **contract-heavy-smbs**). Pipe a permit's address + trade into the matching lead-finder to enrich the property owner or the contractor into a full outreach record.

### Legal & responsible use

- **Public records.** Building permits and contractor licenses are public-by-law government records. We access them logged-out, with no accounts, no cookies, and no paywalls bypassed.
- **PII minimized.** Contractor and applicant *business* names + cities are public on the filing and kept. Personal phone numbers are dropped; owner names are retained only as the public filing exposes them (many portals already redact to a business name).
- **Intent, not surveillance.** A fresh permit is a buying/intent signal for suppliers, installers, and insurers - use it for legitimate B2B outreach and risk assessment, not to harass property owners. Respect each portal's terms and your own counsel's guidance, especially when enabling the opt-in CAPTCHA solver.

### AI agents & MCP

The `overview` dataset view is a narrow, token-efficient slice for LLM agents. A paid **MCP twin** (`mcp-building-permits-intel`) exposes `search_permits`, `get_permit`, `lookup_contractor`, and `trade_signals` as MCP tools for Claude, Cursor, OpenAI Assistants, and LangChain - and is x402 (USDC on Base) / Skyfire ready for token-less agentic payments.

# Actor input Schema

## `mode` (type: `string`):

permit\_search = fetch recent/filtered permits per jurisdiction with contractor links + property clusters. permit\_profile = direct lookup for specific permits by number. contractor\_lookup = resolve a contractor's permit footprint across jurisdictions. trade\_signals = score permits into ranked, cross-sell-ready buying signals for the trades (solar/roofing/HVAC/pool/electrical/plumbing).

## `jurisdictions` (type: `array`):

Jurisdiction codes. 5 fully-parsed feeds return real data: CHICAGO, AUSTIN, NYC, SF, SEATTLE (the default set, all open Socrata feeds). A national catalog of major metros/counties (Houston, Phoenix, Dallas, Denver, Boston, DC, Philadelphia, ...) and state contractor-license boards (CSLB, FL DBPR, AZ ROC, OR CCB, ...) is registered with the correct portal + access method (parser pending - they return a documented jurisdiction\_pending note). Pass \['FULL'] for the verified metros or \['ALL'] for every catalog jurisdiction.

## `all_jurisdictions` (type: `boolean`):

Shortcut to query every registered jurisdiction (overrides the list). Fully-parsed jurisdictions return permits; catalog jurisdictions return a documented jurisdiction\_pending note.

## `query` (type: `string`):

Optional free-text keyword matched against each feed's permit type + work description, e.g. 'roof', 'solar', 'pool', 'new construction'. Leave empty in permit\_search/trade\_signals to pull the most recent permits.

## `address` (type: `string`):

Optional street-name / address fragment to filter permits to a corridor or block, e.g. 'BROADWAY'.

## `trades` (type: `array`):

Trades to score/keep, e.g. \['solar','roofing','hvac','pool','electrical','plumbing']. In trade\_signals mode these are the trades scored; in permit\_search they filter the permits. Leave empty to use the high-intent default set (solar, roofing, pool, hvac, electrical, plumbing).

## `permit_numbers` (type: `array`):

Look up specific permits directly (permit\_profile mode). Each item: {"jurisdiction": "SF", "permit\_number": "202409060229"}. Hard cap of 50.

## `contractors` (type: `array`):

Contractor/business names to resolve a permit footprint for in contractor\_lookup mode, e.g. \['KangaRoof', 'Turner Construction']. Hard cap of 25.

## `date_from` (type: `string`):

Optional lower bound on the permit issue date.

## `date_to` (type: `string`):

Optional upper bound on the permit issue date.

## `min_value` (type: `integer`):

Optional: keep only permits whose declared job valuation is at least this many dollars. Drops permits with no reported valuation.

## `include_valuation` (type: `boolean`):

When on (default), each permit that carries a parsed dollar valuation charges a valuation\_signal. Turn off for a value-free permit-count run.

## `max_results_per_jurisdiction` (type: `integer`):

Hard cap on permits returned per jurisdiction. Default 25.

## `contact_email` (type: `string`):

Optional operator contact added to request headers. Good-citizen practice; not required.

## `monitor_webhook_url` (type: `string`):

When this actor runs under an Apify Schedule (monitor mode), post the change digest (new permits, status changes, finals) to this Slack-compatible webhook URL.

## `use_apify_proxy` (type: `boolean`):

Route portal requests through Apify Proxy. The DATACENTER tier handles the open Socrata feeds; a RESIDENTIAL tier is provisioned for the anti-bot escalation legs.

## `use_browser_fallback` (type: `boolean`):

When a portal serves a Cloudflare/CAPTCHA/403 challenge, automatically escalate: switch to the RESIDENTIAL proxy and retry with curl\_cffi Chrome TLS impersonation, then (if available) a headless browser. The open Socrata feeds never need this; the catalog Accela/EnerGov jurisdictions do.

## `browser_cdp_url` (type: `string`):

Optional. CDP/WebSocket endpoint of an already-running anti-detect (UC-mode / real Chrome) browser. When set, the browser tier connects to it so it passes the Cloudflare-fronted Accela/EnerGov catalog jurisdictions. Can also be set as the BROWSER\_CDP\_URL env var. CAPTCHA-gated portals additionally need an opt-in solver: set CAPTCHA\_SOLVER\_PROVIDER (2captcha|capsolver) + CAPTCHA\_SOLVER\_KEY as actor env secrets - off by default.

## `apify_proxy_groups` (type: `array`):

Override the auto-selected proxy group. Leave empty to let the actor pick DATACENTER for the open feeds.

## `concurrency` (type: `integer`):

Parallel jurisdiction fetches. Gov portals are rate-sensitive; default 4.

## Actor input object example

```json
{
  "mode": "trade_signals",
  "jurisdictions": [
    "CHICAGO",
    "AUSTIN",
    "NYC",
    "SF",
    "SEATTLE"
  ],
  "all_jurisdictions": false,
  "query": "roof",
  "address": "",
  "trades": [
    "solar",
    "roofing",
    "hvac",
    "pool"
  ],
  "permit_numbers": [],
  "contractors": [],
  "date_from": "",
  "date_to": "",
  "min_value": 0,
  "include_valuation": true,
  "max_results_per_jurisdiction": 15,
  "contact_email": "",
  "monitor_webhook_url": "",
  "use_apify_proxy": true,
  "use_browser_fallback": true,
  "browser_cdp_url": "",
  "apify_proxy_groups": [],
  "concurrency": 4
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

Narrow, token-efficient slice of every record. Consumer: LLM agents (Claude, GPT, LangChain tools), MCP hosts, trades dashboards.

## `datasetItemsPermits` (type: `string`):

Full normalized permits with contractor, valuation, and address. Consumer: CRM enrichment, supplier prospecting, RAG ingest.

## `datasetItemsTradeSignals` (type: `string`):

Permits scored into ranked buying-intent signals for the trades. Consumer: solar/roofing/HVAC sales teams, suppliers.

## `datasetItemsMcp` (type: `string`):

First 50 overview records as a clean JSON array. Wrap on the agent side in an MCP tool-call envelope. Consumer: MCP servers, Claude Desktop, Cursor, OpenAI Assistants tool calls.

## `datasetItemsCsv` (type: `string`):

Spreadsheet-friendly export of the overview view. Consumer: sales ops, Excel / Google Sheets users.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "trade_signals",
    "jurisdictions": [
        "CHICAGO",
        "AUSTIN",
        "NYC",
        "SF",
        "SEATTLE"
    ],
    "query": "roof",
    "trades": [
        "solar",
        "roofing",
        "hvac",
        "pool"
    ],
    "max_results_per_jurisdiction": 15
};

// Run the Actor and wait for it to finish
const run = await client.actor("seibs.co/building-permits-intel").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "trade_signals",
    "jurisdictions": [
        "CHICAGO",
        "AUSTIN",
        "NYC",
        "SF",
        "SEATTLE",
    ],
    "query": "roof",
    "trades": [
        "solar",
        "roofing",
        "hvac",
        "pool",
    ],
    "max_results_per_jurisdiction": 15,
}

# Run the Actor and wait for it to finish
run = client.actor("seibs.co/building-permits-intel").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "trade_signals",
  "jurisdictions": [
    "CHICAGO",
    "AUSTIN",
    "NYC",
    "SF",
    "SEATTLE"
  ],
  "query": "roof",
  "trades": [
    "solar",
    "roofing",
    "hvac",
    "pool"
  ],
  "max_results_per_jurisdiction": 15
}' |
apify call seibs.co/building-permits-intel --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=seibs.co/building-permits-intel",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Building Permits & Contractor License Intel",
        "description": "Unifies US building-permit + contractor-license portals into one schema: type, valuation, status, contractor, address. Value layer: contractor linking, property clustering, and scored high-intent trade signals (solar/roofing/HVAC) that cross-sell into trades lead-gen. Logged-out public records.",
        "version": "0.1",
        "x-build-id": "dVfAbPtkqjc9zcP5d"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/seibs.co~building-permits-intel/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-seibs.co-building-permits-intel",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/seibs.co~building-permits-intel/runs": {
            "post": {
                "operationId": "runs-sync-seibs.co-building-permits-intel",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/seibs.co~building-permits-intel/run-sync": {
            "post": {
                "operationId": "run-sync-seibs.co-building-permits-intel",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "mode"
                ],
                "properties": {
                    "mode": {
                        "title": "Mode",
                        "enum": [
                            "permit_search",
                            "permit_profile",
                            "contractor_lookup",
                            "trade_signals"
                        ],
                        "type": "string",
                        "description": "permit_search = fetch recent/filtered permits per jurisdiction with contractor links + property clusters. permit_profile = direct lookup for specific permits by number. contractor_lookup = resolve a contractor's permit footprint across jurisdictions. trade_signals = score permits into ranked, cross-sell-ready buying signals for the trades (solar/roofing/HVAC/pool/electrical/plumbing).",
                        "default": "permit_search"
                    },
                    "jurisdictions": {
                        "title": "Jurisdictions to query",
                        "maxItems": 40,
                        "type": "array",
                        "description": "Jurisdiction codes. 5 fully-parsed feeds return real data: CHICAGO, AUSTIN, NYC, SF, SEATTLE (the default set, all open Socrata feeds). A national catalog of major metros/counties (Houston, Phoenix, Dallas, Denver, Boston, DC, Philadelphia, ...) and state contractor-license boards (CSLB, FL DBPR, AZ ROC, OR CCB, ...) is registered with the correct portal + access method (parser pending - they return a documented jurisdiction_pending note). Pass ['FULL'] for the verified metros or ['ALL'] for every catalog jurisdiction.",
                        "default": [
                            "CHICAGO",
                            "AUSTIN",
                            "NYC",
                            "SF",
                            "SEATTLE"
                        ],
                        "items": {
                            "type": "string"
                        }
                    },
                    "all_jurisdictions": {
                        "title": "Query every catalog jurisdiction",
                        "type": "boolean",
                        "description": "Shortcut to query every registered jurisdiction (overrides the list). Fully-parsed jurisdictions return permits; catalog jurisdictions return a documented jurisdiction_pending note.",
                        "default": false
                    },
                    "query": {
                        "title": "Keyword query (permit type / work description)",
                        "type": "string",
                        "description": "Optional free-text keyword matched against each feed's permit type + work description, e.g. 'roof', 'solar', 'pool', 'new construction'. Leave empty in permit_search/trade_signals to pull the most recent permits.",
                        "default": ""
                    },
                    "address": {
                        "title": "Address contains",
                        "type": "string",
                        "description": "Optional street-name / address fragment to filter permits to a corridor or block, e.g. 'BROADWAY'.",
                        "default": ""
                    },
                    "trades": {
                        "title": "Target trades (trade_signals + permit_search filter)",
                        "type": "array",
                        "description": "Trades to score/keep, e.g. ['solar','roofing','hvac','pool','electrical','plumbing']. In trade_signals mode these are the trades scored; in permit_search they filter the permits. Leave empty to use the high-intent default set (solar, roofing, pool, hvac, electrical, plumbing).",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "permit_numbers": {
                        "title": "Direct permit references (jurisdiction + permit number)",
                        "maxItems": 50,
                        "type": "array",
                        "description": "Look up specific permits directly (permit_profile mode). Each item: {\"jurisdiction\": \"SF\", \"permit_number\": \"202409060229\"}. Hard cap of 50.",
                        "default": []
                    },
                    "contractors": {
                        "title": "Contractor names (contractor_lookup)",
                        "maxItems": 25,
                        "type": "array",
                        "description": "Contractor/business names to resolve a permit footprint for in contractor_lookup mode, e.g. ['KangaRoof', 'Turner Construction']. Hard cap of 25.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "date_from": {
                        "title": "Issued on/after (YYYY-MM-DD)",
                        "type": "string",
                        "description": "Optional lower bound on the permit issue date.",
                        "default": ""
                    },
                    "date_to": {
                        "title": "Issued on/before (YYYY-MM-DD)",
                        "type": "string",
                        "description": "Optional upper bound on the permit issue date.",
                        "default": ""
                    },
                    "min_value": {
                        "title": "Minimum declared valuation (USD)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Optional: keep only permits whose declared job valuation is at least this many dollars. Drops permits with no reported valuation.",
                        "default": 0
                    },
                    "include_valuation": {
                        "title": "Charge + emit valuation signals",
                        "type": "boolean",
                        "description": "When on (default), each permit that carries a parsed dollar valuation charges a valuation_signal. Turn off for a value-free permit-count run.",
                        "default": true
                    },
                    "max_results_per_jurisdiction": {
                        "title": "Max permits per jurisdiction",
                        "minimum": 1,
                        "maximum": 200,
                        "type": "integer",
                        "description": "Hard cap on permits returned per jurisdiction. Default 25.",
                        "default": 25
                    },
                    "contact_email": {
                        "title": "Contact email (optional)",
                        "type": "string",
                        "description": "Optional operator contact added to request headers. Good-citizen practice; not required.",
                        "default": ""
                    },
                    "monitor_webhook_url": {
                        "title": "Monitor webhook URL (Slack / email, optional)",
                        "type": "string",
                        "description": "When this actor runs under an Apify Schedule (monitor mode), post the change digest (new permits, status changes, finals) to this Slack-compatible webhook URL.",
                        "default": ""
                    },
                    "use_apify_proxy": {
                        "title": "Use Apify Proxy",
                        "type": "boolean",
                        "description": "Route portal requests through Apify Proxy. The DATACENTER tier handles the open Socrata feeds; a RESIDENTIAL tier is provisioned for the anti-bot escalation legs.",
                        "default": true
                    },
                    "use_browser_fallback": {
                        "title": "Anti-bot escalation (curl_cffi + browser)",
                        "type": "boolean",
                        "description": "When a portal serves a Cloudflare/CAPTCHA/403 challenge, automatically escalate: switch to the RESIDENTIAL proxy and retry with curl_cffi Chrome TLS impersonation, then (if available) a headless browser. The open Socrata feeds never need this; the catalog Accela/EnerGov jurisdictions do.",
                        "default": true
                    },
                    "browser_cdp_url": {
                        "title": "Warm browser CDP endpoint (for browser-required jurisdictions)",
                        "type": "string",
                        "description": "Optional. CDP/WebSocket endpoint of an already-running anti-detect (UC-mode / real Chrome) browser. When set, the browser tier connects to it so it passes the Cloudflare-fronted Accela/EnerGov catalog jurisdictions. Can also be set as the BROWSER_CDP_URL env var. CAPTCHA-gated portals additionally need an opt-in solver: set CAPTCHA_SOLVER_PROVIDER (2captcha|capsolver) + CAPTCHA_SOLVER_KEY as actor env secrets - off by default.",
                        "default": ""
                    },
                    "apify_proxy_groups": {
                        "title": "Proxy groups (optional override)",
                        "type": "array",
                        "description": "Override the auto-selected proxy group. Leave empty to let the actor pick DATACENTER for the open feeds.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "concurrency": {
                        "title": "Max concurrent requests",
                        "minimum": 1,
                        "maximum": 8,
                        "type": "integer",
                        "description": "Parallel jurisdiction fetches. Gov portals are rate-sensitive; default 4.",
                        "default": 4
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
