Developers · API v1
Screen a location from your own systems
One endpoint. Post a point in England, get back the same constraint survey the app shows — every layer checked, every source that failed, the vintages, the attribution and the site score. Built for acquisition pipelines, CRMs and dashboards that would rather not have a person retyping results.
A layer missing from results while its source is listed in failures was not checked. It is not clear. Upstream services go down; when one does, we still answer 200, we still return everything we did check, and we tell you exactly what we could not. failures is always present — an empty array on a clean survey — and status is "partial" whenever it is not empty.
If your integration renders a verdict, render the partial state too. Treating silence as absence is the one failure mode of this API that produces a confidently wrong answer about a real site.
Keys belong to an organisation on the Practice plan. The practice owner creates them under Account → Organisation → API. A key is shown once, at creation — we store a hash of it and cannot show it to you again. Lost it? Rotate it.
Send it as Authorization: Bearer pc_live_…, or as X-API-Key: pc_live_… if your client is happier with that. There is no session, no cookie and no OAuth flow. Tier and subscription state are re-checked on every single request, so revoking a key — or a subscription lapsing — takes effect on the next call.
Treat a key like a password: server-side only, never in a browser bundle or a mobile app. Name each key after the system that uses it, so revoking one when somebody leaves does not take the rest down with it.
Send either a coordinate pair or a postcode. A coordinate wins if you send both — a postcode resolves to the centroid of the postcode unit, which can sit in a different field, flood cell or parcel than the asset you mean.
lat,lng— WGS84 decimal degrees. Both or neither.postcode— a UK postcode, e.g."CB2 1TN".radius— metres. Default 100, maximum 500.
England only. planning.data.gov.uk is MHCLG data and covers England; Scotland, Wales and Northern Ireland are out of scope, not a coverage gap. A point or postcode outside England is refused with a 422 rather than screened against datasets that do not describe it.
curl -X POST https://planningconstraints.com/api/v1/screen \
-H "Authorization: Bearer pc_live_…" \
-H "Content-Type: application/json" \
-d '{ "lat": 52.2053, "lng": 0.1218, "radius": 100 }'Response (abridged)
{
"request_id": "0f1a…",
"api_version": "2026-07",
"surveyed_at": "2026-07-30T09:14:22.183Z",
"status": "partial",
"notice": "One or more sources did not answer. Every layer listed in `failures` was NOT checked — treat it as unknown, never as clear.",
"query": { "lat": 52.2053, "lng": 0.1218, "radius_m": 100, "postcode": null },
"summary": {
"constraints": 3,
"blockers": 1,
"material": 2,
"findings": ["Planning framework", "Heritage"],
"clear_domains": ["Flood & water", "Ground & subsurface"],
"not_covered": 1,
"layers_checked": 41
},
"results": [
{
"definition_id": "green-belt",
"name": "Green Belt",
"domain": "planning-framework",
"subgroup": "designation",
"band": "constraint",
"weight": "blocker",
"count": 1,
"entities": [{ "name": "Cambridge Green Belt", "reference": "…" }],
"vintage": "2025-11"
},
{
"definition_id": "flood-risk-zone",
"name": "Flood risk zone",
"domain": "flood-water",
"subgroup": "fluvial",
"band": "constraint",
"weight": "material",
"count": 0
}
],
"failures": [
{
"source_id": "historic-england",
"label": "Listed buildings (Historic England)",
"definition_ids": ["listed-building"]
}
],
"score": { "version": "v1", "total": 62, "partial": true, "contributions": [] },
"meta": {
"api_version": "2026-07",
"data_vintages": { "green-belt": "2025-11" },
"attribution": ["Contains public sector information licensed under the Open Government Licence v3.0"],
"quota": { "included": 1000, "used": 143, "remaining": 857, "resets_at": "2026-08-01T00:00:00.000Z", "month": "2026-07" }
}
}Reading the response
results[]— every layer that was checked, including the ones that found nothing.count: 0means checked and clear. A layer that is absent entirely was not checked.failures[]— sources that did not answer, each with thedefinition_idsits silence covers. Always present.summary— the panel’s own headline counts, computed by the same code the app and the bulk CSV use, so the three never disagree.score— the Site Score at this point, with its contributions. Point mode: no redline, so every layer counts at full presence weight.meta.data_vintages— publication vintage per layer. Stale data must not read as current.meta.attribution— see below. Not optional.
Errors
| Status | reason | Meaning |
|---|---|---|
| 401 | invalid-key | No credential, malformed, unknown, or revoked. All four are deliberately indistinguishable. |
| 403 | practice-required | The key’s organisation is not on the Practice plan. |
| 403 | subscription-lapsed | The organisation’s subscription has lapsed. Keys are not revoked — reactivate and the same keys work again. |
| 422 | invalid-request | Missing or malformed location, radius over the maximum, or a point outside England. Nothing is fetched and nothing is charged. |
| 429 | rate-limited | Burst limit (60 requests a minute, per key). `Retry-After` is in seconds. |
| 402 | quota-exhausted | The monthly screen allowance is spent. `Retry-After` and `X-Quota-Reset` both point at the reset instant. |
| 503 | capacity | A product-wide daily ceiling was hit. Nothing was charged against your quota. |
| 500 | server-error | The survey could not be run at all. Nothing is charged. Quote the `request_id`. |
Every error carries error (prose), reason (machine-readable, switch on this) and request_id. Quote the request id and we can find the exact call.
Limits, quota and latency
- Burst: 60 requests a minute, per key. Over it: 429 with
Retry-After. - Monthly: 1,000 screens included per organisation per calendar month (UTC), shared across all of its keys. Over it: 402.
- Headers on every response:
X-Quota-Limit,X-Quota-Remaining,X-Quota-Reset. Pace yourself from these rather than from the refusal.
Expect seconds, not milliseconds. A screen fans out to around eighteen live government sources and waits for them; that is what makes the answer current rather than a cached guess. Budget 30 seconds, call it from a queue or a background job rather than inside a user-facing request, and never inside a page render.
The data in every response is public-sector information licensed to us, and the display obligations travel with it. meta.attribution carries the exact strings owed by the layers in that response — if you display the data to anyone, you must reproduce them. See Licences & attribution.
No ownership data crosses this API. HM Land Registry CCOD/OCOD content — proprietor names, title numbers, addresses — is deliberately out of scope for v1 and appears in no response. It is licensed to us under bespoke terms rather than the Open Government Licence, and a machine-consumable surface for it needs designing rather than defaulting.
Versioning
Every response is stamped api_version: "2026-07". Changes to v1 are additive — new fields may appear, existing ones keep their meaning. Anything breaking mints /api/v2; v1 does not change under you.
Not in v1, and each for a stated reason: redline polygons (point and radius only), ownership data, batch endpoints (use bulk CSV screening in the app), webhooks, OAuth and IP allowlists. Tell us which one you need — the order they arrive in is decided by who asks.