plannexus.ioplannexus.io
SearchCoverageWhy us
HelpAPI Docs
Sign inSign up
plannexus.ioplannexus.io
SearchCoverageWhy us
HelpAPI Docs
Sign inSign up

API Documentation

PlanNexus provides a RESTful API for accessing planning application data from councils across the UK.

Authentication

All API requests require an API key passed via the X-API-Key header.

Example requestcurl
curl https://api.plannexus.io/v1/applications \
 -H"X-API-Key: pn_live_your_key_here" \
 -G -d"q=extension" -d"postcode=SW1"

Rate Limiting

Rate limits are communicated via response headers:

TierRateMonthlyPrice
Free10/min1,000£0
Starter60/min50,000£99/mo
Pro300/min150,000£299/mo
EnterpriseCustomUnlimitedCustom

Endpoints

GET/v1/applications

Search and filter planning applications with full-text search, postcode filtering, and faceted results.

Parameters

qstringFull-text search query
postcodestringPostcode prefix filter (e.g. SW1, E8)
statusstringFilter by status (comma-separated)
application_typestringFilter by type
authority_iduuidFilter by authority
date_received_fromdateReceived on or after (YYYY-MM-DD)
date_received_todateReceived on or before
sortstringSort field (default: date_received)
orderstringasc or desc (default: desc)
pageintPage number (default: 1)
per_pageintResults per page (default: 25, max: 100)
GET/v1/applications/nearby

Find planning applications near a geographic point.

Parameters

latfloatLatitude (-90 to 90)
lngfloatLongitude (-180 to 180)
radiusintRadius in metres (default: 1000, max: 50000)
GET/v1/applications/{id}

Get full details for a single planning application.

GET/v1/applications/{id}/documents

List documents associated with an application.

GET/v1/applications/{id}/history

Get the status change history for an application.

GET/v1/authorities

List all local authorities in the database.

POST/v1/subscriptions

Create a webhook subscription to receive notifications for new applications or status changes.

Parameters

webhook_urlstringHTTPS URL to receive payloads
filter_postcodesstring[]Postcode prefix filters
eventsstring[]Event types: new_application, status_change, decision_made
GET/v1/subscriptions

List your webhook subscriptions.

Official SDKs

Thin, typed clients around the REST API. Both honour Retry-After on 429 automatically and ship TypeScript/Python types for every response shape.

Python — pip install plannexuspython
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"])
Node / TS — npm install plannexustypescript
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);
}

Raw HTTP examples

Pythonpython
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']}")
JavaScriptjavascript
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}`);
});

Response envelope & pagination

List endpoints (applications, authorities, subscriptions) return a wrapped envelope:

List response shapejson
{
"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.

Error codes

StatusMeaningWhat to do
400Malformed request / bad queryRead the detail field; fix the input
401Missing / invalid API keyAdd X-API-Key header; rotate if lost
403Feature requires a higher tier (e.g. webhooks)Upgrade at /dashboard/billing
404Resource not foundCheck the id / reference
429Rate limit / quota exhaustedHonour the Retry-After header; retry after
5xxServer errorRetry with exponential backoff; persistent 5xx → email support

Webhooks

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.

Event types

  • new_application — a council published a new application
  • status_change — an existing application's status moved (e.g. validated → under-consultation)
  • decision_made — an application was decided (approved/refused/withdrawn)

Payload format

POST to your webhook URLjson
{
"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" }
}

Headers

  • Content-Type : application/json
  • X-PlanNexus-Signature : sha256=<hex-digest> — HMAC-SHA256 over the raw body, keyed by your webhook secret
  • X-PlanNexus-Event — same as payload.event; handy for routing before parsing the body

Verifying signatures

Pythonpython
import 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)
Node.jsjavascript
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);
}

Delivery & retries

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.

Interactive API Explorer

Try the API directly in your browser with our Swagger UI.

Open Swagger UI