PlanNexus provides a RESTful API for accessing planning application data from councils across the UK.
All API requests require an API key passed via the X-API-Key header.
curl https://api.plannexus.io/v1/applications \
-H"X-API-Key: pn_live_your_key_here" \
-G -d"q=extension" -d"postcode=SW1"Rate limits are communicated via response headers:
| Tier | Rate | Monthly | Price |
|---|---|---|---|
| Free | 10/min | 1,000 | £0 |
| Starter | 60/min | 50,000 | £99/mo |
| Pro | 300/min | 150,000 | £299/mo |
| Enterprise | Custom | Unlimited | Custom |
/v1/applicationsSearch and filter planning applications with full-text search, postcode filtering, and faceted results.
Parameters
qstringFull-text search querypostcodestringPostcode prefix filter (e.g. SW1, E8)statusstringFilter by status (comma-separated)application_typestringFilter by typeauthority_iduuidFilter by authoritydate_received_fromdateReceived on or after (YYYY-MM-DD)date_received_todateReceived on or beforesortstringSort field (default: date_received)orderstringasc or desc (default: desc)pageintPage number (default: 1)per_pageintResults per page (default: 25, max: 100)/v1/applications/nearbyFind planning applications near a geographic point.
Parameters
latfloatLatitude (-90 to 90)lngfloatLongitude (-180 to 180)radiusintRadius in metres (default: 1000, max: 50000)/v1/applications/{id}Get full details for a single planning application.
/v1/applications/{id}/documentsList documents associated with an application.
/v1/applications/{id}/historyGet the status change history for an application.
/v1/authoritiesList all local authorities in the database.
/v1/subscriptionsCreate a webhook subscription to receive notifications for new applications or status changes.
Parameters
webhook_urlstringHTTPS URL to receive payloadsfilter_postcodesstring[]Postcode prefix filterseventsstring[]Event types: new_application, status_change, decision_made/v1/subscriptionsList your webhook subscriptions.
Thin, typed clients around the REST API. Both honour Retry-After on 429 automatically and ship TypeScript/Python types for every response shape.
from plannexus import PlanNexus
with PlanNexus(api_key="pn_live_...") as client:
result = client.applications.search(
q="extension", postcode="SW1", per_page=10
)
for app in result["data"]:
print(app["reference"], app["address"])import { PlanNexus } from "plannexus";
const client = new PlanNexus({ apiKey: "pn_live_..." });
const result = await client.applications.search({
q: "extension",
postcode: "SW1",
per_page: 10,
});
for (const app of result.data) {
console.log(app.reference, app.address);
}import httpx
client = httpx.Client(
base_url="https://api.plannexus.io/v1",
headers={"X-API-Key": "pn_live_your_key"}
)
# Search for extensions in SW1
resp = client.get("/applications", params={
"q": "extension",
"postcode": "SW1",
"per_page": 10
})
data = resp.json()
for app in data["data"]:
print(f"{app['reference']} - {app['address']}")
print(f" Status: {app['status']}")
print(f" Received: {app['date_received']}")
const response = await fetch(
"https://api.plannexus.io/v1/applications? " +
new URLSearchParams({ q: "extension", postcode: "SW1" }),
{
headers: {"X-API-Key": "pn_live_your_key" },
}
);
const { data, meta } = await response.json();
console.log(`Found ${meta.total} applications`);
data.forEach((app) => {
console.log(`${app.reference} - ${app.address}`);
});List endpoints (applications, authorities, subscriptions) return a wrapped envelope:
{
"data": [ /* array of records */ ],
"meta": {
"total": 4218,
"page": 1,
"per_page": 25,
"pages": 169
}
}Single-record endpoints return the bare object. Pagination is page-based with page and per_page query params — `per_page` caps at 100.
| Status | Meaning | What to do |
|---|---|---|
| 400 | Malformed request / bad query | Read the detail field; fix the input |
| 401 | Missing / invalid API key | Add X-API-Key header; rotate if lost |
| 403 | Feature requires a higher tier (e.g. webhooks) | Upgrade at /dashboard/billing |
| 404 | Resource not found | Check the id / reference |
| 429 | Rate limit / quota exhausted | Honour the Retry-After header; retry after |
| 5xx | Server error | Retry with exponential backoff; persistent 5xx → email support |
Create a subscription via POST /v1/subscriptions (or the Webhooks page in your dashboard). PlanNexus POSTs a JSON payload to your endpoint whenever a matching application triggers a subscribed event.
new_application — a council published a new applicationstatus_change — an existing application's status moved (e.g. validated → under-consultation)decision_made — an application was decided (approved/refused/withdrawn){
"event": "new_application",
"timestamp": "2026-04-23T09:15:42.108Z",
"data": {
"application_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"reference": "23/04521/FUL",
"authority_id": "018e1c50-...",
"address": "14 Bishopsgate, London EC2N 4AW",
"postcode": "EC2N 4AW",
"description": "Erection of a single-storey rear extension.",
"status": "validated",
"decision": null,
"application_type": "householder",
"date_received": "2026-04-23" }
}Content-Type : application/jsonX-PlanNexus-Signature : sha256=<hex-digest> — HMAC-SHA256 over the raw body, keyed by your webhook secretX-PlanNexus-Event — same as payload.event; handy for routing before parsing the bodyimport hmac, hashlib
def verify(body: bytes, header: str, secret: str) -> bool:
expected ="sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header)import { createHmac, timingSafeEqual } from "crypto";
export function verify(body, header, secret) {
const expected =
"sha256=" +
createHmac("sha256", secret).update(body).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(header);
return a.length === b.length && timingSafeEqual(a, b);
}A 2xx response is treated as delivered. Anything else counts toward a consecutive-failure tally; a subscription is auto-paused after 10 consecutive failures. Paused subscriptions stop receiving events — resume them from the dashboard once your endpoint is healthy. Click Send test on a subscription to fire a synthetic event: "test" payload — test deliveries don't count against the failure tally.
Try the API directly in your browser with our Swagger UI.
Open Swagger UI