# Planning Applications & Building Permits Scraper | Leads (`skyfishdev/london-planning-leads`) Actor

Find planning and building permit projects in London, Nottingham, Ireland, NSW, San Francisco and Toronto. Choose from 161 city, council and region presets. Filter dates and work types, export source records and monitor changes. Official public data; coverage and dates labelled.

- **URL**: https://apify.com/skyfishdev/london-planning-leads.md
- **Developed by:** [Ibrahim](https://apify.com/skyfishdev) (community)
- **Categories:** Real estate, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.74 / 1,000 project delivereds

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

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Planning & Permit Leads - 5 Countries

![Planning Leads logo](https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/67rOspnZJFx7Yhf7I-actor-FUZsL7vXLZlALQs8l-yVPxz7wwLH-planning-leads-logo.png)

Find extension, loft and alteration projects from official planning and building permit data across five countries. Choose a city, council or region; filter application dates and work types; then export to a spreadsheet or monitor changes. Every result preserves its source, country, date basis and coverage notes. Application links and site coordinates are returned only when supplied.

**Verified sources in five countries:** United Kingdom (London and Nottingham), Ireland (31 participating councils), Australia (124 NSW council presets), United States (San Francisco) and Canada (Toronto). These are 161 city/council/region presets, not 161 separate cities. Choose `city` in the input. Coverage is limited to the specified authorities; it does not claim whole-country coverage outside the Irish participating-authority feed. Projects are not verified customers or evidence of contractor intent, and works may already have started.

### International coverage

| Country / source | Scope | Application date used | Important limits |
| --- | --- | --- | --- |
| United Kingdom - GLA | London planning authorities | Validation date | Existing borough filters and monitor history continue |
| United Kingdom - Nottingham | Nottingham City Council | Case date | Council area, not all Nottinghamshire |
| Ireland - NPAD | 31 participating local authorities | Received date | Council feeds can lag; see live authority freshness in RUN_STATS |
| Australia - NSW Online DA | New South Wales; 124 council presets | Lodgement date | Broad development categories, not full proposals; choose alteration |
| United States - San Francisco DBI | City/county building permit applications | Permit creation date | Building permits differ from planning permission; one matching address per permit |
| Canada - Toronto Building | Active building permit applications | Application date | Cleared permits absent; no site coordinates/radius support |

London remains the default. For UK radius searches use a full postcode or coordinates. For Ireland, NSW and San Francisco, leave postcode empty and optionally provide both latitude and longitude. Without a centre, query the full selected council or region. Toronto is city-wide only. Use `workTypes: ["alteration"]` for NSW; the API supplies categories such as alterations/additions and cannot prove that a specific extension or loft is proposed. Missing individual application links stay null; no tracking URL is invented.

#### Ireland example

```json
{"city":"Ireland","workTypes":["extension","loft"],"recentDays":30,"maxResults":25}
```

Choose any participating council directly, for example `Galway City Council`, `Limerick County Council`, `Waterford City and County Council`, `Kildare County Council` or `Cork County Council`. Dublin City, Fingal and Cork City have observed upstream delays; a small or empty recent result set is not proof of no activity. Every Irish run checks the latest application and synchronization dates observed within its requested date window for selected authorities. Missing authorities are marked unknown, and feeds lagging more than 30 days receive a coverage warning. Retention/retrospective work and extensions of permission duration are excluded by default.

#### Australia example

```json
{"city":"Newcastle City Council","workTypes":["alteration"],"recentDays":30,"maxResults":25}
```

Choose `New South Wales` for the regional feed, or a specific council such as `Council of the City of Sydney`, `Wollongong City Council`, `Albury City Council`, `City of Parramatta Council` or `Coffs Harbour City Council`. Council names represent administrative areas, not arbitrary city-radius boundaries. Modification/review applications are excluded unless amendments are enabled.

#### United States and Canada examples

```json
{"city":"San Francisco","workTypes":["extension","loft","alteration"],"recentDays":30,"maxResults":25}
```

```json
{"city":"Toronto","workTypes":["extension","loft","alteration"],"recentDays":30,"maxResults":25}
```

Toronto revisions are excluded by default. San Francisco and Toronto records are explicitly building permit applications. An issued/approved permit may concern works already started. No applicant, owner, builder, phone or email fields are requested from these feeds.

#### Source attribution

- Ireland: Department of Housing, Local Government and Heritage / participating Irish local authorities, [National Planning Application Dataset](https://opendata.housing.gov.ie/dataset/planning-application-points5), [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). This Actor transforms the published fields and filters proposals.
- Australia: Contains NSW Planning Portal data, NSW Department of Planning, Housing and Infrastructure, [Online DA Data](https://www.data.nsw.gov.au/data/dataset/online-da-data-api), Creative Commons Attribution. Fields are transformed and filtered; no government endorsement.
- United States: City and County of San Francisco, Department of Building Inspection, [Building Permits](https://data.sf.gov/Housing-and-Buildings/Building-Permits/i98e-djp9), [PDDL](https://opendatacommons.org/licenses/pddl/1-0/).
- Canada: City of Toronto, Toronto Building, [Active Building Permits](https://open.toronto.ca/dataset/building-permits-active-permits/). Contains information licensed under the [Open Government Licence - Toronto](https://open.toronto.ca/open-data-licence/).

Requests remain paced and bounded. Large queries can produce an explicitly partial run due to candidate, time, output or spending caps. A national/regional selector is not an exhaustive unbounded export. Sources are live rather than frozen; additions during pagination can be picked up by later monitor runs. Monitoring sees only projects still present in the source and date window, and never infers deletions.

### Quick start

Paste this input to find extension and loft projects around a London-area postcode:

```json
{
  "postcode": "TW5 9LN",
  "radiusKm": 5,
  "workTypes": ["extension", "loft"],
  "recentDays": 30,
  "maxResults": 25
}
```

Or search a whole borough:

```json
{
  "boroughs": ["Hounslow"],
  "workTypes": ["extension", "loft"],
  "recentDays": 30,
  "maxResults": 100
}
```

Download the default dataset as JSON, CSV, or Excel. `RUN_STATS` reports the date window, candidate count, delivered records, skipped unchanged records, source freshness observed among matches, and any output, scan, or spending limit.

### Nottingham example

```json
{
  "city": "Nottingham",
  "postcode": "NG1 5FS",
  "radiusKm": 5,
  "workTypes": ["extension", "loft"],
  "recentDays": 30,
  "maxResults": 25
}
```

Coverage is the city council area, not all Nottinghamshire. London filters by validation date; Nottingham filters by the published case date. Every row includes `applicationDate` and `dateBasis`; `validatedDate` remains null when the council does not separately supply one. Nottingham refresh dates are preserved as synchronization timestamps, not invented record-update timestamps. London is the default city, and existing London monitor histories continue.

### What you get

| Field | Meaning |
| --- | --- |
| `reference`, `authority`, `applicationId` | Source identity and council reference |
| `address`, `postcode` | Project site supplied by the source |
| `description` | Council-published proposal, normalized whitespace |
| `workTypes` | Extension / loft keywords detected in the proposal |
| `status`, `decision`, `stage` | Source text plus a conservative normalized stage |
| `applicationDate`, `dateBasis`, `validatedDate`, `decisionDate` | Source application/case date and its explicit basis; validation date only when supplied |
| `latitude`, `longitude`, `distanceKm` | Source site coordinates and postcode-radius distance |
| `councilUrl` | Original application link, or null with a coverage note |
| `sourceUpdatedAt`, `sourceSyncedAt`, `retrievedAt` | Source and retrieval timestamps |
| `change` | `result` in export mode; `new` or `updated` in monitoring |
| `warnings` | Missing links/coordinates and opt-in retrospective/amendment notes |

No applicant names, personal phone numbers, or email enrichment are collected. Council links are retained, not crawled; planning drawings and PDFs are not copied.

### Scheduled monitoring

Save a task with:

```json
{
  "boroughs": ["Hounslow"],
  "workTypes": ["extension", "loft"],
  "recentDays": 90,
  "monitor": true,
  "monitorKey": "hounslow-extensions",
  "maxResults": 100,
  "maxCandidates": 2000
}
```

Schedule it daily or weekly through Apify. The first run delivers matching projects. Later runs return only previously unseen projects or changes to proposal, address, status, decision, or other useful project fields. Source refresh timestamps alone do not trigger a paid update.

The baseline is private to the running user's Apify account and query filters. Use the same account, monitor name, and filters to continue it. Changing filters starts a separate baseline. Up to 20,000 projects are retained per monitor; choose a new name to reset. Cloud monitoring uses a queue lock to reject overlapping runs of the same monitor. A terminated run's lock expires after ten minutes. Local export works without a cloud monitor.

Only records successfully written to the dataset advance the baseline. A failed or capped run keeps undelivered projects eligible for later runs. Interrupted dataset writes are recovered on resurrection without knowingly charging for the same record twice. Monitoring checks the configured source-date window: a project that ages out is not reported as deleted, and later updates to that project require a wider window.

### Filters and limits

- `city` selects a supported city, region or council. `postcode` accepts a full UK postcode for UK sources only; paired `latitude`/`longitude` support other countries except Toronto; `radiusKm` is 0.1–50 km. Searches near a boundary still return the selected supported authorities only. Radius searches omit projects without source coordinates rather than inventing a location.
- `boroughs` selects London authority names; empty means all London authorities. Leave it empty for every other city/region. This includes special planning authorities listed in the input picker.
- `recentDays` defaults to 30. Optional `dateFrom` / `dateTo` use YYYY-MM-DD and override the rolling window. Future dates and windows over 366 days are rejected.
- `statuses` defaults to any. Source statuses are also returned verbatim. Unknown statuses are not guessed into a specific stage.
- Retrospective work and amendments/conditions are excluded by explicit text patterns by default. Enable their input switches if useful. Keyword matching is an aid, not a certification of scope or project stage.
- `maxResults` defaults to 100, with a global maximum of 5,000 delivered records. `maxCandidates` defaults to 2,000, maximum 10,000 inspected source records. The run also has a bounded work budget of about three minutes.
- Source searches are live, not an immutable snapshot. Late additions or edits can be picked up by the next monitor run. Absence of results does not prove no projects exist.

A successful run can have zero results when filters match nothing or a monitor has no new changes. `partial` means an explicit preview, candidate, time, or spending cap. Source/network/schema failures fail the run visibly while retaining usable records and diagnostics.

### Pricing

Launch price: **$2.49 per 1,000 delivered project records**, with 10%, 20%, and 30% Bronze/Silver/Gold discounts. Startup is $0.00005 per memory-based start event. The live Pricing tab is authoritative; do not rely on this README if the configured price differs.

Platform usage is included. Unchanged monitor records, filtered-out candidates, and empty result sets have no dataset-record charge. New projects and changed projects each count as one delivered record. Maximum total charge and output limits are honored before another record is written.

### API example

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('skyfishdev/london-planning-leads').call({
  boroughs: ['Hounslow'], workTypes: ['extension', 'loft'],
  recentDays: 30, maxResults: 25
}, { maxTotalChargeUsd: 0.25 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Source and reliability

Nottingham: [official council planning points feed](https://services.arcgis.com/yvqphKcf9bBSnjX1/ArcGIS/rest/services/Planning_Application_Points/FeatureServer), licensed under the [Open Government Licence v3](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/). © Nottingham City Council; contains OS data © Crown copyright and database right. Original council links are returned when available. Nottingham pages use a bounded, sorted object-ID list; identities for billing and monitoring use the council reference, not unstable object IDs.

Source: [Greater London Authority Planning London Datahub](https://www.london.gov.uk/what-we-do/planning/digital-planning-0/planning-london-datahub), accessed through its [documented guest API](https://www.london.gov.uk/sites/default/files/planninglondondatahub_api_connection_technical_documentation_v1.pdf). Postcode centres come from [Postcodes.io](https://postcodes.io/). Upstream completeness, coordinate accuracy, and update delays vary. Original source evidence and timestamps remain in every record. This Actor is independent of the GLA and local councils.

Requests are paced, response sizes and timeouts are bounded, and Retry-After cooldowns are honored. Unsupported endpoints, malformed source results, timed-out searches, and failed search shards are rejected. No proxy, login, or external API key is needed for the documented public endpoints.

### Development

`npm ci`, `npm test`, `node tools/metadata.mjs`, and `apify validate-schema`. Use `node tools/live-check.mjs examples/postcode.json` for a bounded real-source check. Cloud verification must additionally exercise monitoring twice and verify the datasets, billing, and public publication state.

# Actor input Schema

## `city` (type: `string`):

Choose a city, national/regional feed or specific Irish/NSW council. UK and Ireland supply planning records; NSW development applications; San Francisco and Toronto building permits. Source dates and freshness differ. For NSW choose alterations/additions. Toronto has no radius support.

## `postcode` (type: `string`):

Optional full postcode: TW5 9LN (London) or NG1 5FS (Nottingham). Radius uses source-supplied site coordinates. Leave blank for the whole selected city. Choose the matching city first.

## `latitude` (type: `number`):

Optional site-search centre for any supported country. Provide longitude too; leave postcode blank. Example Galway: 53.2707; Newcastle NSW: -32.9283.

## `longitude` (type: `number`):

Optional site-search centre paired with latitude. Example Galway: -9.0568; Newcastle NSW: 151.7817. Coordinates are not a substitute for council coverage.

## `radiusKm` (type: `number`):

Distance around a UK postcode or supplied latitude/longitude. Omit all centre fields for the full selected council or region. Only supported-authority records with site coordinates can pass a radius filter.

## `boroughs` (type: `array`):

Optional authority filter. Empty means all London authorities in the official source. Missing or delayed source records can affect coverage.

## `workTypes` (type: `array`):

Keyword matching against the source proposal or categories. NSW supplies broad categories rather than detailed proposals: choose alteration for alterations/additions there. This does not establish contractor intent.

## `recentDays` (type: `integer`):

Include the last N calendar days, including today, using the explicit source date basis (validation, case, received or lodged). Delayed upstream feeds can omit recent records. Explicit dates override this window.

## `dateFrom` (type: `string`):

Optional YYYY-MM-DD application start date. Maximum date window is 366 days. No future dates.

## `dateTo` (type: `string`):

Optional YYYY-MM-DD application end date. Defaults to today. No future dates.

## `statuses` (type: `array`):

Optional normalized stage filter. Any keeps all stages, including unknown source statuses. Council status and decision are also returned verbatim. Approved includes lawful-development and prior-approval outcomes.

## `includeRetrospective` (type: `boolean`):

Off by default: excludes proposals explicitly described as retrospective or already completed. Source descriptions cannot guarantee work has not started.

## `includeAmendments` (type: `boolean`):

Off by default: excludes explicit non-material amendments, condition variations and approval-of-details records.

## `monitor` (type: `boolean`):

Enable scheduled monitoring. First run delivers matching projects; subsequent runs deliver only new projects or changed details. Private baseline is isolated by user and filters. No removals are inferred. Cloud only; overlapping runs of the same monitor are rejected.

## `monitorKey` (type: `string`):

Use the same name and filters to continue a monitor, or a new name to start again. 1–40 letters, digits, hyphens or underscores. Up to 20,000 tracked projects per monitor.

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

Global output and billing cap. When reached, RUN_STATS reports a partial preview. Repeat a monitor run to collect remaining unseen projects.

## `maxCandidates` (type: `integer`):

Bounds source requests and runtime, including records later filtered out or already delivered. Raise for large monitors. Hitting the cap reports partial coverage.

## Actor input object example

```json
{
  "city": "London",
  "radiusKm": 5,
  "boroughs": [],
  "workTypes": [
    "extension",
    "loft"
  ],
  "recentDays": 30,
  "statuses": [
    "any"
  ],
  "includeRetrospective": false,
  "includeAmendments": false,
  "monitor": false,
  "monitorKey": "default",
  "maxResults": 100,
  "maxCandidates": 2000
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runStats` (type: `string`):

No description

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("skyfishdev/london-planning-leads").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("skyfishdev/london-planning-leads").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 '{}' |
apify call skyfishdev/london-planning-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,skyfishdev/london-planning-leads"
        }
    }
}
```

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/FUZsL7vXLZlALQs8l/builds/xyscPldZjfg0D1YD0/openapi.json
