# Austin Permit Lead Deltas (`snapperwapper/austin-permit-lead-deltas`) Actor

Privacy-minimized issued construction permit deltas from the official City of Austin Socrata dataset.

- **URL**: https://apify.com/snapperwapper/austin-permit-lead-deltas.md
- **Developed by:** [snapperwapper](https://apify.com/snapperwapper) (community)
- **Categories:** Automation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## Austin Permit Lead Deltas

Create privacy-minimized, vertical-specific change feeds from the City of Austin's official **Issued Construction Permits** Socrata dataset (`3syk-w9eu`). The Actor classifies roofing, HVAC, solar, and general-construction permits; preserves stable permit identity and useful project facts; and emits factual `NEW` or `CHANGED` deltas with evidence.

> **Public-record disclaimer:** Permit records are informational only. Verify every record with the City of Austin before making business, compliance, eligibility, or outreach decisions.

### What it does

- Calls only `https://data.austintexas.gov/resource/3syk-w9eu.json` through the official Socrata API—no browser, alternate source, or arbitrary URL.
- Selects issued permits (`issue_date IS NOT NULL`) and supports inclusive issue-date bounds.
- Deterministically classifies **roofing**, **HVAC**, **solar**, and **general construction** from permit type/class/work/description signals.
- Uses `permit_number` as stable permit ID, deduplicates pages, and normalizes project type, description, issue/status dates, valuation, square footage, district, city/ZIP, company, and official source link.
- Privacy-minimizes by default: no exact residential address, coordinates, contractor/applicant personal name, or phone number.
- Allows sensitive public fields only when `includeSensitivePublicFields: true`, with an explicit output and log warning.
- Persists permit snapshots and emits only `NEW` and `CHANGED` rows with `before`, `after`, and `changedFields` when delta detection is enabled.
- Bounds results, page size, maximum pages, timeout, and retries; source failure becomes an `ERROR` row.
- Includes a synthetic, no-network `demoMode`.

### Input

```json
{
  "verticals": ["roofing", "hvac", "solar", "general"],
  "issuedFrom": "2026-08-01",
  "issuedTo": "2026-08-12",
  "maxResults": 1000,
  "pageSize": 500,
  "maxPages": 200,
  "detectChanges": true,
  "includeSensitivePublicFields": false,
  "requestTimeoutSecs": 30,
  "maxRequestRetries": 3,
  "demoMode": false
}
```

| Field | Default | Meaning |
| --- | --- | --- |
| `verticals` | all four | Required classifications: `roofing`, `hvac`, `solar`, `general`. A permit matching any selected vertical is retained. |
| `issuedFrom` | omitted | Inclusive issue date (`YYYY-MM-DD`). |
| `issuedTo` | omitted | Inclusive issue date (`YYYY-MM-DD`); implemented as `< next-day midnight` in SoQL. |
| `maxResults` | `1000` | Global cap after classification/deduplication (1–100,000). |
| `pageSize` | `500` | Socrata `$limit` (1–1,000). |
| `maxPages` | `200` | Explicit pagination safety bound (1–1,000). |
| `detectChanges` | `true` | Emit only `NEW` and `CHANGED` permits against persistent state. |
| `includeSensitivePublicFields` | `false` | Explicit opt-in for exact address, coordinates, contractor/applicant personal names, and phones. |
| `requestTimeoutSecs` | `30` | Per-request timeout (1–120 seconds). |
| `maxRequestRetries` | `3` | Retries for timeout/network failures, HTTP 408/429, and 5xx (0–10). |
| `demoMode` | `false` | Use a bundled synthetic permit and perform no network request. |

The endpoint, selected columns, order, and SoQL structure are constructed internally. Users cannot supply a host or arbitrary URL.

### Privacy behavior

By default the Actor keeps lower-risk project and business facts:

- permit/project IDs and official record link;
- vertical, permit type/class, work class, and project description;
- issue/application/status/expiration dates;
- valuation, square footage, floors, units, district, jurisdiction;
- city, state, ZIP; and
- contractor trade and **company** name.

It excludes:

- exact `original_address1` / `permit_location`;
- latitude and longitude;
- contractor/applicant personal names; and
- contractor/applicant phone numbers.

If you explicitly set `includeSensitivePublicFields: true`, the Actor emits those available fields, marks each row with `sensitivePublicFieldsIncluded: true`, adds `privacyWarning`, and logs the same warning:

> Sensitive public records fields are included by explicit request. They may contain personal information and exact residential locations; use, store, and contact people only where lawful and appropriate.

Public availability does not automatically make every reuse, enrichment, solicitation, or retention practice appropriate. Review applicable laws, City terms, and your purpose before enabling this option.

### Output

```json
{
  "recordType": "PERMIT",
  "changeType": "NEW",
  "source": "City of Austin Open Data",
  "sourceDatasetId": "3syk-w9eu",
  "permitId": "2026-103662 BP",
  "projectId": "13752787",
  "verticals": ["ROOFING"],
  "permitType": "Building Permit",
  "permitTypeCode": "BP",
  "permitClass": "R- 435 Renovations/Remodel",
  "permitClassMapped": "Residential",
  "workClass": "Repair",
  "description": "Reroofing with Class A roofing material",
  "appliedDate": "2026-08-10T00:00:00.000Z",
  "issueDate": "2026-08-10T00:00:00.000Z",
  "status": "Active",
  "statusDate": "2026-08-10T00:00:00.000Z",
  "valuation": 25000,
  "councilDistrict": "8",
  "jurisdiction": "AUSTIN FULL PURPOSE",
  "city": "AUSTIN",
  "state": "TX",
  "postalCode": "78739",
  "contractorTrade": "General Contractor",
  "contractorCompanyName": "Example Roofing Company",
  "sourceUrl": "https://abc.austintexas.gov/web/permit/public-search-other?t_detail=1&t_selected_folderrsn=13752787",
  "disclaimer": "Public permit records are informational only; verify details with the City of Austin before making business, compliance, eligibility, or outreach decisions.",
  "after": { "status": "Active", "valuation": 25000 },
  "fetchedAt": "2026-08-12T12:00:00.000Z"
}
```

Unavailable values are omitted, not guessed. `ERROR` rows retain official source/dataset provenance, the disclaimer, a bounded source error, and timestamp.

### Vertical classification

Classification is intentionally explainable and conservative:

- **ROOFING:** reroof/roof/roofing/shingle/roof-membrane terms.
- **HVAC:** mechanical permit type or HVAC, air conditioning, heat pump/system, or furnace terms.
- **SOLAR:** solar, photovoltaic, PV array, energy-storage, or battery-storage terms.
- **GENERAL:** building permits and general construction/remodel/addition/finish-out/foundation signals only when no more specific vertical matched.

A permit can have multiple specialist verticals (for example solar plus HVAC). Generic plumbing/electrical records without selected project signals are excluded. This classification is useful triage, not a guarantee that work is open, purchasable, or suitable for contact.

### Delta semantics

State is stored as `AUSTIN_PERMIT_STATE` and keyed by `permit_number`:

- **NEW** — stable permit ID absent from baseline.
- **CHANGED** — one or more normalized tracked facts changed.
- **No row** — unchanged current permit when `detectChanges` is enabled.

The Actor does not infer cancellation or deletion from disappearance. The source is an issued-permit dataset, date filters are bounded, and a missing row does not prove revocation. Source errors do not overwrite the baseline. Fingerprints exclude observation timestamps.

### Safe demo and local verification

The default no-input local run uses `fixtures/demo-input.json` and the clearly synthetic `DEMO-2099-0001 BP` response:

```bash
npm install
npm test
npm start
```

The demo never calls the network and contains no exact address, coordinates, personal name, or phone. Set `demoMode: false` for a real Actor run.

### Scheduling and integrations

Use a stable Apify Task/key-value store and schedule daily or hourly. Send `NEW`/`CHANGED` records through webhooks or integrations to internal review queues, CRMs, Sheets, Slack, or your own analytics. Keep human verification before contact or operational action.

### Limitations

- This is an independent Actor, not an official City of Austin product.
- Dataset fields, refresh cadence, historical coverage, status meaning, and source links can change. Verify current records at the official link.
- Classification is keyword/type based and can produce false positives or miss unusual wording; evaluate on your intended segment.
- `maxResults` can require scanning multiple unfiltered source pages because privacy-conscious classification happens locally.
- Public records can contain inaccurate, stale, or personal information. Do not infer ownership, consent, eligibility, need, or willingness to buy.
- Follow City/Socrata terms and applicable privacy, marketing, telephony, retention, and anti-discrimination requirements.

### Local test coverage

Fixture-driven tests make no live calls and cover four verticals, edge exclusions, source normalization, stable identity, privacy default/opt-in warnings, `NEW`/unchanged/`CHANGED` behavior, before/after evidence, official endpoint and SoQL bounds, pagination, deduplication, max results, retries, source errors, input/schema bounds, and the no-network demo entrypoint.

# Actor input Schema

## `verticals` (type: `array`):

Keep issued permits classified into one or more selected verticals.

## `issuedFrom` (type: `string`):

Inclusive issue date in YYYY-MM-DD format.

## `issuedTo` (type: `string`):

Inclusive issue date in YYYY-MM-DD format.

## `maxResults` (type: `integer`):

Global maximum permit rows after classification and deduplication.

## `pageSize` (type: `integer`):

Official Socrata page size.

## `maxPages` (type: `integer`):

Safety bound on source pagination.

## `detectChanges` (type: `boolean`):

Persist stable permit snapshots and output NEW or CHANGED permits only.

## `includeSensitivePublicFields` (type: `boolean`):

Explicitly include exact residential address, coordinates, contractor/applicant personal names, and phone numbers. Disabled by default; enabling emits a privacy warning and requires lawful, appropriate use.

## `requestTimeoutSecs` (type: `integer`):

Timeout per official Socrata request in seconds.

## `maxRequestRetries` (type: `integer`):

Retries for timeouts, HTTP 408/429, network failures, and 5xx responses.

## `demoMode` (type: `boolean`):

Use the bundled synthetic response and make no network request.

## Actor input object example

```json
{
  "verticals": [
    "roofing",
    "hvac",
    "solar"
  ],
  "maxResults": 1000,
  "pageSize": 500,
  "maxPages": 200,
  "detectChanges": true,
  "includeSensitivePublicFields": false,
  "requestTimeoutSecs": 30,
  "maxRequestRetries": 3,
  "demoMode": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

Privacy-minimized Austin permit records and change evidence.

# 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 = {
    "verticals": [
        "roofing",
        "hvac",
        "solar"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("snapperwapper/austin-permit-lead-deltas").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 = { "verticals": [
        "roofing",
        "hvac",
        "solar",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("snapperwapper/austin-permit-lead-deltas").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "verticals": [
    "roofing",
    "hvac",
    "solar"
  ]
}' |
apify call snapperwapper/austin-permit-lead-deltas --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,snapperwapper/austin-permit-lead-deltas"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Pj9fylh7VGw3WLfi4/builds/wFCSh9gwjxZAKUphN/openapi.json
