# Los Angeles Building Permits Scraper (`crawlerbros/los-angeles-building-permits-scraper`) Actor

Scrape LA Dept. of Building & Safety permits. Search/filter by permit type, sub-type, category, status, office, zip, street, valuation, or issue date; lookup by permit number, address, or contractor. Free Socrata API, no login.

- **URL**: https://apify.com/crawlerbros/los-angeles-building-permits-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Automation, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## 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

## Los Angeles Building Permits Scraper

Scrape **Los Angeles building permits** straight from the City of Los Angeles Department of Building & Safety (LADBS) official permit dataset. Search and filter by permit type, sub-type, category, status, issuing office, zip code, street, occupancy group, valuation, or issue date — or look up specific permits by permit number, property address, contractor name, or assessor parcel (Book-Page-Parcel / AIN). HTTP-only via the public LA Open Data (Socrata) API. No auth, no proxy.

### What this actor does

- **Five modes:** `search`, `byPermitNumbers`, `byAddress`, `byContractor`, `byAssessorParcel`
- **Rich filters:** permit type/sub-type/category, status, initiating office, street direction/suffix, occupancy group, zip codes, issue/status date range, valuation range, story count range, minimum residential units, work-description keyword, geo-radius search (lat/lng + radius)
- **Full permit detail:** contractor, principal, and applicant identity; construction valuation and floor area; assessor block/lot/tract; census tract; latitude/longitude
- **Sortable:** by issue date, status date, valuation, number of stories, or residential dwelling units
- **Empty fields are omitted**

### Output per permit

- `permitNumber` — LADBS PCIS permit number
- `referenceOldPermit` — legacy/cross-referenced permit number, if any
- `status`, `statusDate` — current permit status and the date it was last updated
- `permitType`, `permitSubType`, `permitCategory` — e.g. `Bldg-New` / `1 or 2 Family Dwelling` / `Plan Check`
- `projectNumber`, `eventCode`, `initiatingOffice`
- `issueDate` — date the permit was issued
- `addressStart`, `addressEnd`, `addressFractionStart`, `addressFractionEnd` — street-number range covered by the permit
- `streetDirection`, `streetName`, `streetSuffix`, `suffixDirection`
- `unitRangeStart`, `unitRangeEnd`
- `fullAddress`, `city`, `state`, `zipCode`
- `workDescription` — free-text description of the work
- `valuation` — declared construction valuation (USD)
- `floorAreaZoningCode`, `floorAreaBuildingCode` — floor area (sq ft) under LA zoning / building code definitions
- `residentialDwellingUnits`, `numberOfStories`
- `contractorBusinessName`, `contractorAddress`, `contractorCity`, `contractorState`
- `licenseType`, `licenseNumber`, `licenseExpirationDate`
- `principalFirstName`, `principalMiddleName`, `principalLastName`
- `applicantFirstName`, `applicantLastName`, `applicantBusinessName`, `applicantAddress1`, `applicantAddress2`, `applicantAddress3`
- `zone`, `occupancy`, `censusTract`
- `assessorBook`, `assessorPage`, `assessorParcel`, `tract`, `block`, `lot`
- `latitude`, `longitude`
- `sourceUrl` — public API query URL that resolves this exact permit record
- `recordType: "buildingPermit"`, `scrapedAt`

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `search` | `search` / `byPermitNumbers` / `byAddress` / `byContractor` / `byAssessorParcel` |
| `permitNumbers` | array | – | Exact PCIS permit numbers (mode=byPermitNumbers) |
| `streetNumber` | string | – | Building street number (mode=byAddress, optional) |
| `streetName` | string | – | Street name — exact in byAddress, substring filter in search |
| `streetDirection` | string | – | `N` / `S` / `E` / `W` |
| `streetSuffix` | string | – | e.g. `AVE`, `ST`, `BLVD` |
| `contractorName` | string | – | Contractor business name — exact in byContractor, substring filter in search |
| `assessorBook` / `assessorPage` / `assessorParcel` | string | – | LA County Assessor's Identification Number (AIN) components — exact match (mode=byAssessorParcel, all three required) |
| `workDescriptionKeyword` | string | – | Substring match on the work description |
| `permitType` | string | – | `Bldg-New` / `Bldg-Addition` / `Bldg-Alter/Repair` |
| `permitSubType` | string | – | `1 or 2 Family Dwelling` / `Apartment` / `Commercial` |
| `permitCategory` | string | – | `Plan Check` / `No Plan Check` |
| `status` | string | – | Current permit status (27 possible values) |
| `initiatingOffice` | string | – | LADBS district office |
| `occupancy` | string | – | Building-code occupancy group (25 possible values, e.g. `R3 Occ. Group`). Only populated on a minority of permits. |
| `zipCodes` | array | – | One or more 5-digit zip codes |
| `issueDateFrom` / `issueDateTo` | string | – | Issue-date range (`YYYY-MM-DD`) |
| `statusDateFrom` / `statusDateTo` | string | – | Status-date range (`YYYY-MM-DD`) |
| `minValuation` / `maxValuation` | number | – | Declared construction valuation range (USD) |
| `minStories` / `maxStories` | integer | – | Number-of-stories range |
| `minResidentialUnits` | integer | – | Minimum residential dwelling units |
| `nearLatitude` / `nearLongitude` | number | – | Center point for a geo-radius search. Both must be set together. |
| `radiusMeters` | number | `500` | Radius (in meters) around `nearLatitude`/`nearLongitude`. Only used when both coordinates are set. |
| `sortBy` | string | `issueDateDesc` | Sort order — issue date / status date / valuation / number of stories / residential units, each asc or desc |
| `appToken` | string | – | Optional free Socrata app token to raise rate limits |
| `maxItems` | integer | `50` | Hard cap on emitted records (1–10000) |

#### Example: recent new-construction permits in a zip code

```json
{
  "mode": "search",
  "permitType": "Bldg-New",
  "zipCodes": ["90047"],
  "sortBy": "issueDateDesc",
  "maxItems": 50
}
```

#### Example: lookup by permit number

```json
{
  "mode": "byPermitNumbers",
  "permitNumbers": ["20010-30000-00390", "19010-20000-04796"]
}
```

#### Example: permits at a specific address

```json
{
  "mode": "byAddress",
  "streetNumber": "6500",
  "streetName": "SEABLUFF",
  "streetSuffix": "DR"
}
```

#### Example: all permits for a contractor

```json
{
  "mode": "byContractor",
  "contractorName": "OWNER-BUILDER",
  "maxItems": 100
}
```

#### Example: lookup by assessor parcel (Book-Page-Parcel / AIN)

```json
{
  "mode": "byAssessorParcel",
  "assessorBook": "2405",
  "assessorPage": "003",
  "assessorParcel": "032"
}
```

#### Example: high-value apartment permits issued in a date range

```json
{
  "mode": "search",
  "permitSubType": "Apartment",
  "issueDateFrom": "2022-01-01",
  "issueDateTo": "2022-12-31",
  "minValuation": 500000,
  "sortBy": "valuationDesc"
}
```

#### Example: permits within 500m of a point (downtown LA)

```json
{
  "mode": "search",
  "nearLatitude": 34.0522,
  "nearLongitude": -118.2437,
  "radiusMeters": 500,
  "sortBy": "issueDateDesc"
}
```

### Use cases

- **Real estate research** — track new construction, additions, and remodels in a neighborhood or zip code
- **Contractor intelligence** — pull a contractor's full LA permit history by business name
- **Market analysis** — trend construction valuation and permit volume over time
- **Lead generation** — find recently permitted projects for suppliers, insurers, or service providers
- **Compliance / due diligence** — verify a property's permit history and current permit status before a transaction

### FAQ

**What is the data source?**
The City of Los Angeles Open Data portal (data.lacity.org), specifically the "LA Building Permits" dataset (Socrata id `xnhu-aczu`) published by the LA Department of Building & Safety.

**Is this affiliated with the City of Los Angeles?**
No — this is a third-party actor that reads the City's public open-data API.

**How fresh is the data?**
The source dataset is refreshed periodically by LADBS; this actor always queries the live API, so results reflect whatever is currently published.

**Are there rate limits?**
The public API allows a reasonable number of requests without a token. If you scrape large volumes regularly, you can supply a free Socrata `appToken` to raise your rate limit.

**Why do some permits have the same permit number but different addresses?**
A small number of multi-parcel or multi-building projects in the source data share the same PCIS permit number across rows (e.g. Building A/B of the same project). Each row is still emitted as its own record.

**Why are some fields missing on certain permits?**
LADBS does not populate every field for every permit (e.g. `occupancy` and `residentialDwellingUnits` are rarely filled in for commercial-alteration permits). The source data also uses placeholders like `-` or `NA` for "not provided" on some fields (e.g. applicant/principal names, contractor address); the actor omits the field on that row rather than returning the placeholder text. Empty fields are omitted rather than returned as `null`.

**Why aren't `contractorState` and `licenseType` available as filter dropdowns?**
Both are free-text in the source data with inconsistent values (e.g. license class recorded as `B`, `b`, `01B`, or `C39` for what should be the same class; state recorded with non-standard codes). Rather than offer an inaccurate dropdown, they remain available as plain output fields.

**Why isn't `zone` available as a filter dropdown?**
LA zoning has 1,400+ distinct real codes (e.g. `R1-1`, `RD1.5-1XL`, `R1-1-HPOZ`) — too many and too granular for a stable dropdown. It's still returned as a plain output field on every permit.

**Some permits show unusually high or missing `numberOfStories`/`residentialDwellingUnits` — why?**
The City's source data contains occasional data-entry errors on these two fields (e.g. a small addition recorded with dozens of "stories"). The actor applies sub-type-aware plausibility limits — tight for single-family-home permits, wide for apartment/commercial towers — and omits the field on the rare row that falls outside a realistic range rather than surfacing an obviously mistaken number.

**Can I search near a location instead of an exact address?**
Yes — set `nearLatitude`/`nearLongitude` (and optionally `radiusMeters`, default 500m) to return only permits within that radius of a point. This combines with every other filter, including permit type, status, and date range.

# Actor input Schema

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

What to fetch.

## `permitNumbers` (type: `array`):

Exact PCIS permit numbers to fetch. Example: `20010-30000-00390`.

## `streetNumber` (type: `string`):

Building street number. Example: `6500`.

## `streetName` (type: `string`):

Street name, without direction/suffix. Example: `HARVARD`. Exact match in mode=byAddress; substring match as a filter in mode=search.

## `streetDirection` (type: `string`):

Filter to a single street direction prefix.

## `streetSuffix` (type: `string`):

Filter to a single street-suffix abbreviation.

## `assessorBook` (type: `string`):

LA County Assessor's Identification Number (AIN) "Book" component. Example: `2405`.

## `assessorPage` (type: `string`):

LA County Assessor's Identification Number (AIN) "Page" component. Example: `003`.

## `assessorParcel` (type: `string`):

LA County Assessor's Identification Number (AIN) "Parcel" component. Example: `032`.

## `contractorName` (type: `string`):

Exact contractor business name in mode=byContractor (e.g. `OWNER-BUILDER`); substring filter in mode=search.

## `workDescriptionKeyword` (type: `string`):

Case-insensitive substring match on the permit's work description. Example: `swimming pool`.

## `permitType` (type: `string`):

Filter to a single top-level permit type.

## `permitSubType` (type: `string`):

Filter to a single building-use sub-type.

## `permitCategory` (type: `string`):

Filter to a single review category.

## `status` (type: `string`):

Filter to a single current permit status.

## `initiatingOffice` (type: `string`):

Filter to a single LADBS district office that issued the permit.

## `occupancy` (type: `string`):

Filter to a single building-code occupancy classification. Only populated on a minority of permits (mostly new construction / major alterations) -- most permits omit this field entirely.

## `zipCodes` (type: `array`):

Restrict results to one or more 5-digit LA zip codes. Example: `90047`.

## `issueDateFrom` (type: `string`):

Drop permits issued before this date.

## `issueDateTo` (type: `string`):

Drop permits issued after this date.

## `statusDateFrom` (type: `string`):

Drop permits whose latest status change is before this date.

## `statusDateTo` (type: `string`):

Drop permits whose latest status change is after this date.

## `minValuation` (type: `number`):

Drop permits with a lower declared construction valuation than this.

## `maxValuation` (type: `number`):

Drop permits with a higher declared construction valuation than this.

## `minStories` (type: `integer`):

Drop permits with fewer stories than this.

## `maxStories` (type: `integer`):

Drop permits with more stories than this.

## `minResidentialUnits` (type: `integer`):

Drop permits with fewer residential dwelling units than this.

## `nearLatitude` (type: `number`):

Latitude of a center point. Combine with `nearLongitude` (and optionally `radiusMeters`) to return only permits within a radius of this point. Example: `34.0522` (downtown LA). Both nearLatitude and nearLongitude must be set for this filter to apply.

## `nearLongitude` (type: `number`):

Longitude of a center point. Combine with `nearLatitude` (and optionally `radiusMeters`) to return only permits within a radius of this point. Example: `-118.2437` (downtown LA).

## `radiusMeters` (type: `number`):

Search radius around `nearLatitude`/`nearLongitude`, in meters. Only used when both are set. Defaults to 500m.

## `sortBy` (type: `string`):

Sort order for results.

## `appToken` (type: `string`):

Optional free Socrata app token to raise API rate limits. Get one at https://data.lacity.org/profile/app\_tokens. Not required — the actor works without it.

## `maxItems` (type: `integer`):

Hard cap on emitted records.

## Actor input object example

```json
{
  "mode": "search",
  "permitNumbers": [],
  "streetDirection": "",
  "streetSuffix": "",
  "permitType": "",
  "permitSubType": "",
  "permitCategory": "",
  "status": "",
  "initiatingOffice": "",
  "occupancy": "",
  "zipCodes": [],
  "radiusMeters": 500,
  "sortBy": "issueDateDesc",
  "maxItems": 50
}
```

# Actor output Schema

## `permits` (type: `string`):

Dataset containing all scraped LA building permit records.

# 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": "search",
    "permitNumbers": [],
    "streetDirection": "",
    "streetSuffix": "",
    "permitType": "",
    "permitSubType": "",
    "permitCategory": "",
    "status": "",
    "initiatingOffice": "",
    "occupancy": "",
    "zipCodes": [],
    "radiusMeters": 500,
    "sortBy": "issueDateDesc",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/los-angeles-building-permits-scraper").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": "search",
    "permitNumbers": [],
    "streetDirection": "",
    "streetSuffix": "",
    "permitType": "",
    "permitSubType": "",
    "permitCategory": "",
    "status": "",
    "initiatingOffice": "",
    "occupancy": "",
    "zipCodes": [],
    "radiusMeters": 500,
    "sortBy": "issueDateDesc",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/los-angeles-building-permits-scraper").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 '{
  "mode": "search",
  "permitNumbers": [],
  "streetDirection": "",
  "streetSuffix": "",
  "permitType": "",
  "permitSubType": "",
  "permitCategory": "",
  "status": "",
  "initiatingOffice": "",
  "occupancy": "",
  "zipCodes": [],
  "radiusMeters": 500,
  "sortBy": "issueDateDesc",
  "maxItems": 50
}' |
apify call crawlerbros/los-angeles-building-permits-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/los-angeles-building-permits-scraper"
        }
    }
}

```

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/1Yo3r19i7vFaAdijB/builds/WDP9tXW2ylC4YY4oG/openapi.json
