Developer documentation

UK Planning Applications API reference

Structured, classified, source-linked UK planning application data over a REST JSON API. Base URL https://api.plota.co.uk/v1

Authentication

Every request needs an API key, sent as a bearer token (preferred) or the X-API-Key header. Keys are secret - never expose them in client-side code.

shell
curl "https://api.plota.co.uk/v1/applications?postcode=SW11&limit=5" \
  -H "Authorization: Bearer plota_live_your_key"

Endpoints

EndpointPurpose
GET /v1/applicationsSearch applications with filters + cursor pagination
GET /v1/applications/{id}Retrieve one application by public id or numeric id
GET /v1/applications/nearbyApplications near a coordinate or postcode
GET /v1/councilsCovered councils with coverage metadata
GET /v1/categoriesPlota planning categories
GET /v1/alertsSaved alerts belonging to your key’s email
GET /v1/alerts/{id}/matchesApplications matching a saved alert
GET /v1/applications/{id}/historyStatus & decision timeline
POST /v1/exportsBulk export as CSV/JSON (Starter+)
POST /v1/webhooksRegister a signed webhook (Starter+)
GET / DELETE /v1/webhooks/{id}List or remove webhooks
POST /v1/webhooks/{id}/testSend a test event

Query parameters

GET /v1/applications accepts:

ParameterDescription
postcodeFull or partial postcode, e.g. SW11 or SW11 6HB
councilCouncil slug (see /v1/councils), e.g. wandsworth
nationengland, scotland, wales, northern-ireland
categoryOne or more category slugs, comma-separated (see /v1/categories)
qKeyword match in description, address, or reference
statusSubstring match on status, e.g. pending, approved
date_from / date_toFilter by received date, YYYY-MM-DD
limitPage size, capped per plan (Demo 10 → Business 250)
cursorOpaque pagination cursor from meta.next_cursor
formatjson (default) or csv

Pagination

List endpoints use opaque cursor pagination. Pass meta.next_cursor back as cursor to fetch the next page. A null next_cursor means the last page.

application/json
{
  "data": [
    "..."
  ],
  "meta": {
    "limit": 25,
    "count": 25,
    "next_cursor": "eyJzIjoiMjAyNi0wNi0xOCIsImkiOjE4fQ"
  }
}

Rate limits & quotas

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, plus monthly counterparts. A 429 includes Retry-After.

PlanPer minutePer monthMax page
Demo30 / min500 / month10
Starter120 / min20,000 / month50
Pro300 / min100,000 / month100
Business600 / min500,000 / month250
Enterprise1,200 / minCustom500

Errors

Errors use a consistent shape with a request_id you can quote in support.

application/json
{
  "error": {
    "type": "invalid_request",
    "message": "The postcode parameter is invalid.",
    "param": "postcode",
    "request_id": "req_01jz7k8a2p8b"
  }
}
HTTPtypeMeaning
400invalid_requestA parameter is missing or malformed
401authentication_errorMissing, invalid, or revoked API key
403permission_errorYour plan does not allow this resource
404not_foundNo resource with that id
429rate_limit_errorPer-minute or monthly quota exceeded
500server_errorAn unexpected error on our side
501not_implementedA roadmap endpoint not yet live

The application object

application/json
{
  "id": "a1b2c3d4",
  "reference": "2026/2284/FUL",
  "authority": {
    "slug": "wandsworth",
    "name": "Wandsworth"
  },
  "address": "81 Example Road, London SW11 6HB",
  "postcode": "SW11 6HB",
  "description": "Single storey rear extension.",
  "category": {
    "slug": "extensions",
    "label": "Extensions & alterations"
  },
  "categories": [
    {
      "slug": "extensions",
      "label": "Extensions & alterations"
    }
  ],
  "planning_route": "Full planning permission",
  "status": "Pending",
  "stage": "pending",
  "date_received": "2026-06-18",
  "date_validated": "2026-06-20",
  "date_decided": null,
  "location": {
    "lat": 51.4601,
    "lng": -0.1702
  },
  "links": {
    "plota": "https://plota.co.uk/application/a1b2c3d4",
    "council": "https://..."
  },
  "source": "live"
}

Webhooks

Register an HTTPS endpoint with POST /v1/webhooks (linked to a saved alert). Plota POSTs an application.match.created event for each new match, with retries, and auto-disables endpoints that keep failing. Verify each delivery: compute HMAC-SHA256 over ${Plota-Timestamp}.${rawBody} with your webhook secret and compare to the Plota-Signature header.

node
const sig = crypto.createHmac('sha256', secret)
  .update(req.headers['plota-timestamp'] + '.' + rawBody).digest('hex');
if (sig === req.headers['plota-signature']) { /* trusted */ }

Integration guides (Zapier, Make, Slack, Sheets, Airtable, HubSpot, Pipedrive). Manage keys & webhooks in your dashboard. Machine-readable schema: /openapi.json. Building with AI: paste /llms.txt into your assistant, or use the MCP server for live access. Questions? hello@plota.co.uk.