# UK Planning Applications & Site Constraints (Planning Data) (`yadroo/uk-planning-applications`) Actor

Read the UK Planning Data register: planning applications with reference, site address, coordinates, proposal, decision and decision date, plus the designations covering a site — conservation area, article 4, listed building outline, flood risk zone. Filter by council, radius, text or date.

- **URL**: https://apify.com/yadroo/uk-planning-applications.md
- **Developed by:** [Samat Makatov](https://apify.com/yadroo) (community)
- **Categories:** Real estate, Developer tools
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 planning record returneds

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

## UK Planning Applications & Site Constraints (Planning Data)

Read the official UK **Planning Data** platform (`planning.data.gov.uk`, run by the Ministry of Housing, Communities &
Local Government) as flat rows. Two jobs in one actor: the **planning-application register** — council reference, site
address and postcode, coordinates, the proposal text, the decision and its date, a link to the council's own file where
the council publishes one — and a **site constraint check**: which designations cover a coordinate (conservation area,
article 4 direction, listed building outline, tree preservation zone, flood risk zone, green belt and eight more layers).

Filter by council, by a circle around a coordinate, by keyword, by decision wording, by decision date or by reference.
Every date is a UTC calendar date, every row links back to the record it came from. No API key, no proxy, no browser,
no council portal is touched.

Application coverage is partial and the platform says so itself — read [Coverage](#coverage) before you plan a project
around it. Designation layers are national.

### Use cases

- **Planning history of a site (surveyors, developers, architects)**: every application within 300 m–2 km of a
  coordinate, with the proposal, the decision and the date, as the evidence base for a new application.
- **Appeal and refusal research (planning consultants)**: one council filtered to `decisionContains: "refus"`, newest
  decision first, to build a precedent list of what was turned down and why it was proposed.
- **Lead pipelines for trades and suppliers**: approved works that mention `solar`, `heat pump`, `loft conversion` or
  `basement`, each with the site address and, for Adur and Worthing, a direct link to the council's application file.
- **Local reporting and analysis**: one authority and one year (`decisionDateFrom` / `decisionDateTo`) as a
  spreadsheet — the raw material for "how many extensions were refused in 2023".
- **Site constraint checks before buying or extending (owners, conveyancers, solar installers)**: `mode: "constraints"`
  with `radiusMeters: 0` answers "is this spot inside a conservation area, an article 4 direction or a flood risk zone",
  each answer with the designation's name, reference, publishing authority and a GeoJSON boundary link.
- **Heritage screening (heritage consultants, retrofit assessors)**: listed building outlines and conservation areas
  within a radius of an address, to see what sits around a site and not only on it.
- **Keeping a copy in sync**: `onlyNew: true` in a scheduled task writes only records the task has not delivered before,
  so a warehouse table grows by the batch the platform publishes.

### Coverage

The **designation datasets are national** and maintained: at the time of writing the flood-risk-zone layer alone held
780,636 rows, and an article 4 row at the test coordinate carried a register date of 2026-08-10.

The **planning-application register is not national**. The platform marks it "incomplete and not yet ready for use",
and it is a snapshot republished in batches, not a live feed. Counted on 2026-09-19 it held 100,627 applications from
four authorities:

| Council | Short code | Organisation entity | Rows | Site address | Coordinates | Received date | Decision wording | Council file link |
|---|---|---|---|---|---|---|---|---|
| London Borough of Camden | CMD | 90 | 77,499 | yes | yes | no | in `decisionType` (`Granted`, `Refused`, …) | no |
| Worthing Borough Council | WOT | 382 | 13,629 | yes | yes | yes | `decision` (`Approve`) + `decisionType` (`Delegated`) | yes |
| Adur District Council | ADU | 26 | 7,585 | yes | yes | yes | `decision` + `decisionType` | yes |
| Doncaster Metropolitan Borough Council | DNC | 109 | 1,914 | no | no | no | none, only `decisionDate` | no |

Nothing in the actor hard-codes those four. Councils are resolved against the platform's live local-authority register
(379 authorities), so an authority the platform adds later works without an update here. A council that resolves but has
no applications in the register simply returns zero rows, and the run says so.

### Input

Nothing is required. With no input at all the actor reads the register from its start; the prefills (Camden, the
Primrose Hill coordinate, the six everyday designation layers) make the first run a real search.

| Field | Type | Default | Allowed values / notes |
|---|---|---|---|
| `mode` | string | `applications` | `applications` = the register, `constraints` = designations covering a point — see [Modes](#modes) |
| `council` | string | *(prefill `Camden`)* | Name or short code (`Camden`, `London Borough of Camden`, `CMD`). Resolved against the live register — see [Councils](#councils). Empty = every council. Not used in `constraints` |
| `latitude` | number | *(prefill `51.5413`)* | WGS84, 49.5 … 61.0. Required for `constraints` |
| `longitude` | number | *(prefill `-0.1543`)* | WGS84, −9.0 … 2.5, negative west of Greenwich. Works only together with `latitude` |
| `radiusMeters` | integer | `1000` | 0 … 20000. `0` in `constraints` = the exact point; `0` in `applications` is read as 1000 |
| `datasets` | string\[] | *(prefill: the six everyday layers)* | Which designation layers `constraints` asks about — see [Designation datasets](#designation-datasets). Empty = the six defaults |
| `textContains` | string | — | Case-insensitive text in the proposal, the address or the reference (`solar`, `loft conversion`, `BN43`). Applied by this actor over `maxScanRows` |
| `decisionContains` | string | — | Case-insensitive text in either decision field (`granted`, `refus`, `approve`, `withdrawn`, `delegated`) — see [Decision wording](#decision-wording) |
| `decisionDateFrom` | string | — | `YYYY-MM-DD`, inclusive. Applied by this actor over `maxScanRows` |
| `decisionDateTo` | string | — | `YYYY-MM-DD`, inclusive |
| `reference` | string | — | Exact council reference (`2011/0724/C`, `AWDM/1227/21`). Applied by the API, so it is the cheapest lookup |
| `entryDateSince` | string | — | `YYYY-MM-DD`. The date the **platform loaded** the record, not the date it was submitted or decided |
| `onlyNew` | boolean | `false` | Output only entity ids no earlier run with the same input delivered (state in a named key-value store) |
| `maxScanRows` | integer | `5000` | 100 … 100000. How many register rows a run may read before the actor's own filters. Never lower than `maxItems` |
| `sortBy` | string | `sourceOrder` | `sourceOrder`, `decisionDateDesc`, `decisionDateAsc` — sorted among the rows the run read, see [Limits](#limits--faq) |
| `maxItems` | integer | `25` | 1 … 5000 output rows |
| `fields` | string\[] | all | Keep only these output fields, in this order |

Filters combine with AND. **The API applies** council, the circle around the coordinate, `reference` and
`entryDateSince`; **this actor applies** `textContains`, `decisionContains`, the decision-date window and the exact
distance to your coordinate, over the rows the run reads. That split is a property of the source (see
[Limits & FAQ](#limits--faq)), and every run reports it in its `SUMMARY` record.

### Reference

#### Modes

| `mode` | What one row is | Needs | Typical input |
|---|---|---|---|
| `applications` | One planning application from the register | nothing (council / coordinate / reference recommended) | `{"council":"Camden","radiusMeters":800,"latitude":51.5413,"longitude":-0.1543}` |
| `constraints` | One designation that covers the point or the circle | `latitude` + `longitude` | `{"mode":"constraints","latitude":51.5413,"longitude":-0.1543,"radiusMeters":0}` |

In `constraints` mode `council`, `decisionContains` and the decision-date window have nothing to act on and are
reported as unused in the run log and `SUMMARY`; `textContains` is matched against the designation name and reference.

#### Councils

`council` accepts the short code or any form of the name; the value is matched against the platform's live
local-authority register in this order: short code, exact name, name without its legal suffix
("Camden" → "London Borough of Camden"), a name containing what you typed, and finally a small typo budget
("camdon" → Camden, reported in the run status). An authority that was abolished is used only when no active authority
matches, so "Buckinghamshire" resolves to the unitary council and not to the county council that closed in 2020.

Two things never happen: the search is never widened silently, and an ambiguous name never picks a winner. "North"
ends the run with the 18 candidates listed; an unknown name ends it with the five closest names. The four authorities
whose applications the register holds today are in [Coverage](#coverage); every other council resolves but has no
application rows yet.

#### Designation datasets

`datasets` (used by `mode: "constraints"`):

| id | Layer |
|---|---|
| `conservation-area` | Conservation area |
| `article-4-direction-area` | Article 4 direction area (permitted development removed) |
| `listed-building-outline` | Listed building outline |
| `tree-preservation-zone` | Tree preservation zone |
| `scheduled-monument` | Scheduled monument |
| `world-heritage-site` | World heritage site |
| `site-of-special-scientific-interest` | Site of special scientific interest |
| `flood-risk-zone` | Flood risk zone |
| `green-belt` | Green belt |
| `ancient-woodland` | Ancient woodland |
| `brownfield-land` | Brownfield land register entry |
| `national-park` | National park |
| `area-of-outstanding-natural-beauty` | National landscape (AONB) |
| `local-authority-district` | Local authority district |

The first six are the default. A typed id is corrected when it is close enough (`conservation-areas` →
`conservation-area`) and reported; an id the platform does not publish ends the run with the list above. Not every
council has published every layer — a layer with no row at your point simply returns nothing, which is an answer, not
an error.

#### Decision wording

Councils word decisions differently and the platform passes the words through, so the actor does too: `decision` is the
council's decision, `decisionType` its route or its own classification, and `decisionContains` searches both.

`decisionContains` matches a substring, so `refus` also returns Camden's `Part Granted/Refused` rows — outright refusals
are the rows whose wording is exactly `Refused` (Camden) or `Refuse` (Worthing, Adur), and mixed outcomes always carry
`Part` in front.

| Council | `decision` | `decisionType` | `status` |
|---|---|---|---|
| Camden | *(empty — the wording is in `decisionType`)* | `Granted`, `Refused`, `Withdrawn Decision`, `Granted Subject to a Section 106 Legal Agreement`, `No Objection to Works to Tree(s) in CA`, `Approve Works (TPO)`, `Part Granted/Refused`, … | `Final Decision`, `Withdrawn`, `Appeal Decided`, `Appeal Lodged`, `Registered` |
| Worthing, Adur | `Approve`, `Refuse`, `Withdrawn`, `Split`, `Prior not required - prior approval not required` | `Delegated`, `Committee` | `Withdrawn` or empty |
| Doncaster | *(empty)* | *(empty)* | *(empty)* |

Use a fragment, not a whole word: `refus` catches both `Refused` (Camden) and `Refuse` (Worthing/Adur), `grant` catches
`Granted` and `Part Granted/Refused`.

### Examples

**Planning history around a site in Camden (800 m)**

```json
{ "mode": "applications", "council": "Camden", "latitude": 51.5413, "longitude": -0.1543,
  "radiusMeters": 800, "maxScanRows": 3000, "maxItems": 30 }
```

**Refusals in one council, newest decision first**

```json
{ "mode": "applications", "council": "Camden", "decisionContains": "refus",
  "sortBy": "decisionDateDesc", "maxScanRows": 4000, "maxItems": 25 }
```

**Approved works with a link to the council's file (Worthing)**

```json
{ "mode": "applications", "council": "Worthing", "decisionContains": "approve",
  "maxScanRows": 2000, "maxItems": 30 }
```

**Solar work as a sales pipeline (keyword over a whole council)**

```json
{ "mode": "applications", "council": "Worthing", "textContains": "solar",
  "maxScanRows": 15000, "maxItems": 20 }
```

**One council, one year, as a table**

```json
{ "mode": "applications", "council": "Doncaster", "decisionDateFrom": "2023-01-01",
  "decisionDateTo": "2023-12-31", "sortBy": "decisionDateAsc", "maxScanRows": 2000, "maxItems": 25 }
```

**What binds this plot? (exact point)**

```json
{ "mode": "constraints", "latitude": 51.5413, "longitude": -0.1543, "radiusMeters": 0,
  "datasets": ["conservation-area", "article-4-direction-area", "listed-building-outline",
               "tree-preservation-zone", "flood-risk-zone", "green-belt"], "maxItems": 20 }
```

**Listed buildings and conservation areas within 300 m of an address**

```json
{ "mode": "constraints", "latitude": 51.5197, "longitude": -0.127, "radiusMeters": 300,
  "datasets": ["listed-building-outline", "conservation-area"], "maxItems": 25 }
```

**One application by its reference, trimmed to four fields**

```json
{ "mode": "applications", "reference": "2011/0724/C",
  "fields": ["reference", "address", "decision", "decisionDate", "url"], "maxItems": 5 }
```

### Output

One row per application (or per designation). A real row from the cloud run of the first example above (run `nzp1RvTtCtCIe8APz`):

```json
{
  "entityId": 10000021615,
  "dataset": "planning-application",
  "reference": "2015/4843/L",
  "name": null,
  "council": "London Borough of Camden",
  "councilEntity": 90,
  "description": "Erection of a lower ground floor extension and internal alterations namely the formation of a small opening to the side wall to connect to the lower ground floor extension, the removal of non-original partitions, repairs to structural walls and the renewal of internal fixtures and fittings.",
  "address": "11 Prince Albert Road London NW1 7SR",
  "postcode": "NW1 7SR",
  "latitude": 51.537596,
  "longitude": -0.150267,
  "receivedDate": null,
  "decisionDate": "2015-10-09",
  "decision": "Granted",
  "decisionType": "Granted",
  "status": "Final Decision",
  "developmentClassification": null,
  "documentationUrl": null,
  "entryDate": "2025-09-09",
  "url": "https://www.planning.data.gov.uk/entity/10000021615",
  "geojsonUrl": "https://www.planning.data.gov.uk/entity/10000021615.geojson",
  "fetchedAt": "2026-09-19T22:14:18.683Z"
}
```

| Field | Type | Filled when |
|---|---|---|
| `entityId` | number | Always (the platform's own record id); `null` only in the "not found" row of a reference lookup |
| `dataset` | string | Always — `planning-application` or the designation layer the row came from |
| `reference` | string | Applications: the council's reference. Designations: the authority's reference (`CA29`, `LB1694`) |
| `name` | string | Designations (the designation's name). Applications carry no name |
| `council` | string | The authority that published the row, resolved from `councilEntity`; national layers name their body (e.g. Environment Agency) |
| `councilEntity` | number | The publishing organisation's entity id |
| `description` | string | Applications: the proposal text. Camden, Worthing, Adur and Doncaster all fill it |
| `address` | string | Applications, where the council publishes a site address (not Doncaster) |
| `postcode` | string | Parsed out of `address` when it ends in a UK postcode |
| `latitude`, `longitude` | number | Applications: the published point (not Doncaster). Constraints: the coordinate **you asked about**, echoed into every row |
| `receivedDate` | date | Applications: the date the council received it (Worthing and Adur). Designations: the date the designation started |
| `decisionDate` | date | Applications, where the council published a decision date |
| `decision` | string | The council's decision wording; falls back to `decisionType` for councils that record it there |
| `decisionType` | string | The council's own route or classification (`Delegated`, `Committee`, `Granted`) |
| `status` | string | Camden's application status (`Final Decision`, `Appeal Lodged`, …); other councils leave it empty |
| `developmentClassification` | string | Adur and Worthing: the classification URI the council publishes |
| `documentationUrl` | string | Link to the application file on the council's portal — Adur and Worthing only |
| `entryDate` | date | When the platform loaded the record (the batch date, not the application date) |
| `url` | string | The record page on the platform |
| `geojsonUrl` | string | The record's GeoJSON — a point for applications, the boundary polygon for designations |
| `fetchedAt` | date-time | UTC timestamp of the run |
| `found` | boolean | Only on the single row a `reference` lookup writes when the register holds no such reference |

Boundary polygons are never written into rows (one conservation area is 50 KB of WKT) — `geojsonUrl` serves them.

Dataset views: **Planning applications** (overview), **Applications with coordinates** (map), **Designations covering
the site** (constraints).

The `SUMMARY` record in the run's key-value store holds the resolved council, the first request URL, how many rows
matched server-side, how many rows were read, whether `maxScanRows` stopped the scan, how many rows a filter or the
`onlyNew` state dropped, the request count, every input warning and the licence note.

### Use it from code / agents

```bash
curl -X POST "https://api.apify.com/v2/acts/yadroo~uk-planning-applications/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"council":"Camden","latitude":51.5413,"longitude":-0.1543,"radiusMeters":800,"maxItems":30}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/uk-planning-applications').call({
    mode: 'constraints', latitude: 51.5413, longitude: -0.1543, radiusMeters: 0,
    datasets: ['conservation-area', 'article-4-direction-area', 'flood-risk-zone'], maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yadroo/uk-planning-applications").call(run_input={
    "council": "Worthing", "textContains": "heat pump", "maxScanRows": 15000, "maxItems": 20,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

The actor is a thin wrapper over one government API, which makes it easy to hand to an agent as a tool: one JSON input,
one flat row shape for both questions, and `fields` to keep answers short enough for a prompt —
`{"council":"Camden","radiusMeters":500,"latitude":51.5413,"longitude":-0.1543,"fields":["reference","address","description","decision","decisionDate"]}`.

MCP: add `https://mcp.apify.com` to Claude, Cursor or any MCP client and call the `yadroo/uk-planning-applications`
tool with the same JSON input.

### Pricing

Pay per event: **$0.001 per run start + $0.002 per row** written to the dataset. Nothing else is charged — no proxy, no
browser, 256 MB of memory.

| Run | Cost |
|---|---|
| Site check: 3 designations at one coordinate | $0.007 |
| The default 25-row search around a Camden coordinate | $0.051 |
| Keyword pipeline: 20 solar applications out of a whole council | $0.041 |
| A 500-row extract of one council | $1.001 |

Apify tiers reduce the per-row price automatically: Bronze −10 %, Silver −20 %, Gold and above −30 %.

### Limits & FAQ

- **Application coverage is partial.** Four authorities, 100,627 rows, 77 % of them Camden (see
  [Coverage](#coverage)). If you need a council that is not in the register, this actor cannot give it to you — it
  reads the national platform, not council portals.
- **A snapshot, not a live feed.** The register is republished in batches; the rows loaded on 2026-09-09 (Camden) and
  2026-09-07 (Worthing, Adur) carry those dates in `entryDate`. `entryDateSince` and `onlyNew` work on that load date,
  so "new" means "new in the platform's batch", not "submitted this week".
- **No text search in the API.** The platform has no keyword search over proposals, so `textContains`,
  `decisionContains` and the decision-date window are applied to the rows the run reads. `maxScanRows` is that budget:
  15,000 covers Worthing or Adur completely, Camden's 77,499 rows need a higher cap or a coordinate filter. When the
  cap stops a scan the run status and `SUMMARY` say so — a partial answer is never presented as a complete one.
- **No sort in the API either.** Rows arrive in the order the platform ingested them, so `decisionDateDesc` orders what
  the run read, not the whole register. Over a full council scan the two are the same thing; over a capped scan they
  are not.
- **Doncaster rows have no address and no point.** They carry reference, proposal and decision date only, so a
  coordinate filter can never return them, and their rows leave `address`, `postcode`, `latitude`, `longitude` empty.
- **`start_date` filtering is broken upstream.** The platform's "received since" filter returns nothing on this
  dataset even for rows that carry a received date, so the actor does not offer it; filter on `decisionDate` instead,
  or read the council and filter `receivedDate` yourself.
- **Dates are passed through.** A handful of council records carry obviously wrong decision dates (one Worthing row
  says 2046). The actor does not invent or correct them, it only converts to `YYYY-MM-DD` in UTC.
- **Radius is exact.** The circle is sent to the API as a polygon drawn around your coordinate and every returned row
  is then re-measured, so a 300 m search returns nothing 301 m away. Distances use WGS84 great-circle metres.
- **Designation layers are not complete either.** They are national where the data exists, but a council that never
  published, say, its tree preservation zones has none in the platform. No row at your point means "nothing published
  here", not "certainly unconstrained" — a formal search still belongs to the council.
- **No personal data.** The register publishes proposals and site addresses, and those are what the rows carry.
  Applicant and agent names are not in the platform's records and are never collected.
- **Rate limits and blocks.** The platform publishes no rate limit; the actor uses one request at a time with retry and
  backoff, and a normal run makes 2–30 requests. It calls `www.planning.data.gov.uk` and nothing else — the council
  portal links in `documentationUrl` are passed through as text and never fetched.
- **Licence and attribution.** Planning Data is published under the Open Government Licence v3.0,
  © Crown copyright and database right 2026. Keep the attribution when you republish the rows. This actor is not
  affiliated with the Ministry of Housing, Communities & Local Government.

***

Made by **Yadroo**. Sibling actors: [rightmove-uk](https://apify.com/yadroo/rightmove-uk),
[osm-geocode](https://apify.com/yadroo/osm-geocode),
[public-holidays](https://apify.com/yadroo/public-holidays),
[world-bank-indicators](https://apify.com/yadroo/world-bank-indicators),
[rss-to-json](https://apify.com/yadroo/rss-to-json).

# Actor input Schema

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

`applications` reads the planning-application dataset (reference, proposal, address, decision). `constraints` asks which designations cover a coordinate — conservation areas, article 4 directions, listed building outlines, flood risk zones and the other datasets picked below.

## `council` (type: `string`):

Local authority whose applications you want, by name or by its short code ("Camden", "London Borough of Camden", "CMD"). The name is matched against the platform's local-authority register; an unknown or ambiguous name ends the run with the candidates listed. Empty = every council the register holds. Ignored in `constraints` mode.

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

WGS84 latitude of the site. With `longitude` it filters applications to the circle set by `radiusMeters`; in `constraints` mode it is the point that is tested. Required for `constraints`.

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

WGS84 longitude of the site, negative west of Greenwich. Required together with `latitude`.

## `radiusMeters` (type: `integer`):

Size of the circle drawn around the coordinate. 0 in `constraints` mode tests the exact point ("is this spot inside a conservation area?"); above 0 it returns every designation the circle touches. In `applications` mode 0 is treated as 1000.

## `datasets` (type: `array`):

Which designation datasets `constraints` asks about. Empty = the six everyday planning ones: conservation area, article 4 direction, listed building outline, tree preservation zone, flood risk zone, green belt. Not every council has published every layer — a dataset with no row at your point simply returns nothing.

## `textContains` (type: `string`):

Keep applications whose proposal text or site address contains this text, case-insensitive: "solar", "loft conversion", "change of use", a street name or a postcode such as "BN43". The register has no text search of its own, so the filter is applied to the rows this run reads — raise `maxScanRows` to look deeper, and see README → Limits.

## `decisionContains` (type: `string`):

Keep applications whose decision wording contains this text, case-insensitive: "granted", "refused", "approve", "withdrawn", "delegated". Councils word decisions differently — Camden writes "Granted"/"Refused", Adur and Worthing write "Approve" plus a route such as "Delegated". Both fields are searched.

## `decisionDateFrom` (type: `string`):

Earliest decision date, YYYY-MM-DD. Applied to the rows the run reads (`maxScanRows`), because the API cannot filter on decision date.

## `decisionDateTo` (type: `string`):

Latest decision date, YYYY-MM-DD.

## `reference` (type: `string`):

Exact council reference of one application, e.g. "2011/0724/C" (Camden) or "AWDM/1227/21" (Adur). Matched by the API, so it is the cheapest way to resolve a single application; other filters still apply.

## `entryDateSince` (type: `string`):

Keep rows the platform loaded on or after this date, YYYY-MM-DD. This is the load date of the record, not the date the application was submitted or decided — the register is republished in batches.

## `onlyNew` (type: `boolean`):

Remember the entity ids of this actor's previous runs in its key-value store and output only ids that were not there before. Useful for a scheduled task that keeps a copy of a council in sync. The first run outputs everything it finds.

## `maxScanRows` (type: `integer`):

Upper bound on the rows the run pulls from the API before the text, decision and date filters are applied (500 per request). Raise it for a keyword search over a large council, lower it to keep a run short. The run status and the SUMMARY record say how many rows were read and whether the cap stopped the scan.

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

The API has no sort parameter, so sorting is applied to the rows this run collected — with a scan cap, "newest decision first" means newest among the rows read, not newest in the whole register.

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

Stop after this many output rows.

## `fields` (type: `array`):

Keep only these output fields, in this order, e.g. \["reference", "address", "decision", "decisionDate"]. Empty = every field of the mode.

## Actor input object example

```json
{
  "mode": "applications",
  "council": "Camden",
  "latitude": 51.5413,
  "longitude": -0.1543,
  "radiusMeters": 1000,
  "datasets": [
    "conservation-area",
    "article-4-direction-area",
    "listed-building-outline",
    "tree-preservation-zone",
    "flood-risk-zone",
    "green-belt"
  ],
  "onlyNew": false,
  "maxScanRows": 5000,
  "sortBy": "sourceOrder",
  "maxItems": 25
}
```

# Actor output Schema

## `results` (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 = {
    "council": "Camden",
    "latitude": 51.5413,
    "longitude": -0.1543,
    "datasets": [
        "conservation-area",
        "article-4-direction-area",
        "listed-building-outline",
        "tree-preservation-zone",
        "flood-risk-zone",
        "green-belt"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yadroo/uk-planning-applications").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 = {
    "council": "Camden",
    "latitude": 51.5413,
    "longitude": -0.1543,
    "datasets": [
        "conservation-area",
        "article-4-direction-area",
        "listed-building-outline",
        "tree-preservation-zone",
        "flood-risk-zone",
        "green-belt",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("yadroo/uk-planning-applications").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 '{
  "council": "Camden",
  "latitude": 51.5413,
  "longitude": -0.1543,
  "datasets": [
    "conservation-area",
    "article-4-direction-area",
    "listed-building-outline",
    "tree-preservation-zone",
    "flood-risk-zone",
    "green-belt"
  ]
}' |
apify call yadroo/uk-planning-applications --silent --output-dataset

```

## MCP server setup

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

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/YcbVVV8tDZtUlExNs/builds/44hE1pBHL1vmjrU6d/openapi.json
