UK Planning Applications & Site Constraints (Planning Data)
Pricing
from $1.40 / 1,000 planning record returneds
UK Planning Applications & Site Constraints (Planning Data)
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.
Pricing
from $1.40 / 1,000 planning record returneds
Rating
0.0
(0)
Developer
Samat Makatov
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
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 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 conversionorbasement, 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"withradiusMeters: 0answers "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: truein 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 |
council | string | (prefill Camden) | Name or short code (Camden, London Borough of Camden, CMD). Resolved against the live register — see 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. 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 |
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 |
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), 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; 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)
{ "mode": "applications", "council": "Camden", "latitude": 51.5413, "longitude": -0.1543,"radiusMeters": 800, "maxScanRows": 3000, "maxItems": 30 }
Refusals in one council, newest decision first
{ "mode": "applications", "council": "Camden", "decisionContains": "refus","sortBy": "decisionDateDesc", "maxScanRows": 4000, "maxItems": 25 }
Approved works with a link to the council's file (Worthing)
{ "mode": "applications", "council": "Worthing", "decisionContains": "approve","maxScanRows": 2000, "maxItems": 30 }
Solar work as a sales pipeline (keyword over a whole council)
{ "mode": "applications", "council": "Worthing", "textContains": "solar","maxScanRows": 15000, "maxItems": 20 }
One council, one year, as a table
{ "mode": "applications", "council": "Doncaster", "decisionDateFrom": "2023-01-01","decisionDateTo": "2023-12-31", "sortBy": "decisionDateAsc", "maxScanRows": 2000, "maxItems": 25 }
What binds this plot? (exact point)
{ "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
{ "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
{ "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):
{"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
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}'
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();
from apify_client import ApifyClientclient = 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). 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.entryDateSinceandonlyNewwork 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,decisionContainsand the decision-date window are applied to the rows the run reads.maxScanRowsis 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 andSUMMARYsay 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
decisionDateDescorders 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,longitudeempty. start_datefiltering 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 ondecisionDateinstead, or read the council and filterreceivedDateyourself.- 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-DDin 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.ukand nothing else — the council portal links indocumentationUrlare 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, osm-geocode, public-holidays, world-bank-indicators, rss-to-json.