Skip to main content
EirePlan
EndpointsPrecedents API

Site History by Boundary

Search planning history inside a site boundary you already hold or draw on a map.

Search planning history inside a site boundary rather than around a point.

Use this when you already hold the site's extent — a folio parcel, a red-line application area, a title map, or a polygon a user drew on a map — and would otherwise have to approximate it with a radius. The boundary states exactly which site you mean, so a 90 m frontage and a 9 m one are no longer the same query.

Everything about Site History stays as it is. This is an alternative entry point that returns the identical response envelope, so both can render through the same code path.

#Request

POST /ext/v1/precedents/site-history/polygon

#Body

FieldTypeRequiredDescription
geometryobjectYesA GeoJSON Polygon or MultiPolygon geometry object. Not a Feature, not a FeatureCollection — see below.
limitintegerNoMaximum records returned. Default 200, capped at 500.
json
{
  "geometry": {
    "type": "Polygon",
    "coordinates": [[[-6.26400, 53.34500], [-6.26340, 53.34500], [-6.26340, 53.34527], [-6.26400, 53.34527], [-6.26400, 53.34500]]]
  },
  "limit": 200
}

#Geometry Rules

These are the four rules that matter:

  • Coordinate order is [longitude, latitude], not [lat, lon]. This is the GeoJSON convention and it is the reverse of how the lat/lon query parameters are named elsewhere in this API. Getting it backwards is the one mistake that does not raise an error: a ring written as [53.345, -6.264] puts your site in the Indian Ocean and quietly returns zero results. An empty result from a boundary you know is populated is almost always this.
  • Rings must be closed. The last coordinate pair must repeat the first, so a ring needs at least four positions. Fewer than four is rejected with 400; an unclosed ring of four or more is currently rejected further down the stack and surfaces as a 500, so treat closing the ring as your responsibility rather than relying on the error to be descriptive.
  • WGS84 (EPSG:4326) only. Irish Transverse Mercator (EPSG:2157) and Irish Grid (EPSG:29903) coordinates are rejected as out of range with 400. Reproject before sending — most Irish folio and red-line data arrives in ITM.
  • Type must be Polygon or MultiPolygon. Feature, GeometryCollection, LineString, and bare coordinate arrays are all rejected with 400.

Area is capped at 5 km², returning 413. A red-line application boundary is typically a few hundred to a few thousand square metres, so this only bites if you send an estate, a townland, or an accidentally malformed ring whose extent spans the country.

One caveat on that cap: area is measured by summing every ring, so interior rings (holes) add to the measured area instead of subtracting from it. A doughnut-shaped boundary measures larger than it really is. Well under the cap this never matters; near it, send the outer ring alone.

#Sending a Boundary You Already Have

GIS exports almost never hand you a bare geometry. They hand you a Feature or a FeatureCollection, and posting one of those verbatim is the most common 400 on this endpoint. Unwrap it first:

javascript
// From a Feature
const geometry = feature.geometry;

// From a FeatureCollection — first feature
const geometry = featureCollection.features[0].geometry;

// From a Leaflet layer (already WGS84)
const geometry = layer.toGeoJSON().geometry;

A full Feature looks like this. The part you send is the value of geometry and nothing else — properties is discarded and is not echoed back:

json
{
  "type": "Feature",
  "properties": { "site_ref": "INFILL-01", "area_ha": 0.12 },
  "geometry": {
    "type": "Polygon",
    "coordinates": [[[-6.26400, 53.34500], [-6.26340, 53.34500], [-6.26340, 53.34527], [-6.26400, 53.34527], [-6.26400, 53.34500]]]
  }
}

Use a MultiPolygon when one site is split into parts — a plot severed by a laneway, or a landholding in two blocks. Send it as a single request rather than one request per part, so the returned counts describe the whole site:

json
{
  "geometry": {
    "type": "MultiPolygon",
    "coordinates": [
      [[[-6.26400, 53.34500], [-6.26340, 53.34500], [-6.26340, 53.34527], [-6.26400, 53.34527], [-6.26400, 53.34500]]],
      [[[-6.26310, 53.34500], [-6.26260, 53.34500], [-6.26260, 53.34522], [-6.26310, 53.34522], [-6.26310, 53.34500]]]
    ]
  }
}

#Example Request

curl -X POST "https://api.eireplan.ie/ext/v1/precedents/site-history/polygon" \
  -H "Authorization: Bearer ek_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "geometry": {
      "type": "Polygon",
      "coordinates": [[[-6.26400, 53.34500], [-6.26340, 53.34500], [-6.26340, 53.34527], [-6.26400, 53.34527], [-6.26400, 53.34500]]]
    },
    "limit": 200
  }'

#Drawing a Boundary on a Map

Any draw control that emits GeoJSON works. With Leaflet and Leaflet.draw, take the geometry straight off the drawn layer — Leaflet already works in WGS84 and already closes the ring for you:

javascript
const map = L.map("map").setView([53.34513, -6.26370], 19);
const drawnItems = new L.FeatureGroup().addTo(map);

map.addControl(
  new L.Control.Draw({
    draw: { polygon: true, rectangle: true, marker: false, polyline: false, circle: false },
    edit: { featureGroup: drawnItems },
  })
);

map.on(L.Draw.Event.CREATED, async (event) => {
  drawnItems.clearLayers();
  drawnItems.addLayer(event.layer);

  const geometry = event.layer.toGeoJSON().geometry;
  const result = await searchSiteHistory(geometry); // the fetch call above
  renderPlanningHistory(result);
});

If you use MapLibre with Mapbox Draw, draw.getAll() returns a FeatureCollection — unwrap it as shown above before sending.

#Response Shape

Identical to Site History: { "result": { summary, precedents, used_fallbacks, source, … } }. Two fields differ in a way worth handling:

FieldValue hereWhy
result.sourceuser_polygonRecords that the area came from you, not from a geocode or a radius.
result.query_radius_m0No radius was involved. Do not render it as "searched within 0 m".

Every returned precedent carries the strongest match signals, because a hit means the application's point falls inside the area you defined:

FieldValue
match_reasonuser_polygon
confidence_tierS
same_site_confidence0.95
same_site_signals["user_polygon"]

#Example Response

json
{
  "result": {
    "query_radius_m": 0,
    "used_fallbacks": ["user_polygon"],
    "summary": {
      "total_applications": 2,
      "decision_counts": { "GRANTED": 1, "REFUSED": 1 },
      "type_counts": { "PERMISSION": 2 }
    },
    "precedents": [
      {
        "id": "5f8fcb68-2be1-4ce7-8ef4-6a29c2fb4aa8",
        "planning_ref": "DCC-1234-25",
        "address": "15 Example Street, Dublin 2",
        "decision_norm": "granted",
        "match_reason": "user_polygon",
        "confidence_tier": "S",
        "same_site_confidence": 0.95,
        "same_site_signals": ["user_polygon"]
      },
      {
        "id": "f40db59f-91f5-478c-a5ab-5c1b6086f7cd",
        "planning_ref": "DCC-9981-24",
        "address": "15 Example Street, Dublin 2",
        "decision_norm": "refused",
        "match_reason": "user_polygon",
        "confidence_tier": "S",
        "same_site_confidence": 0.95,
        "same_site_signals": ["user_polygon"]
      }
    ],
    "source": "user_polygon"
  }
}

#Putting the Results Back on the Map

The site-history response carries no coordinates — it is a list of records, not a feature collection. To plot the results, fetch geometry per precedent from Get Precedent Details, which returns a GeoJSON point and, where the authority publishes one, a polygon:

javascript
async function toFeatureCollection(precedents, apiKey) {
  const features = await Promise.all(
    precedents.map(async (precedent) => {
      const response = await fetch(
        `https://api.eireplan.ie/ext/v1/precedents/${precedent.id}?geometry=simplified`,
        { headers: { Authorization: `Bearer ${apiKey}` } }
      );
      const { precedent: detail } = await response.json();

      // Prefer the published site boundary; fall back to the address point.
      const geometry = detail.polygon ?? detail.point;
      if (!geometry) return null;

      return {
        type: "Feature",
        geometry,
        properties: {
          planning_ref: detail.planning_ref,
          decision: detail.decision_norm,
          address: detail.address,
        },
      };
    })
  );

  return { type: "FeatureCollection", features: features.filter(Boolean) };
}

const collection = await toFeatureCollection(result.precedents, apiKey);

L.geoJSON(collection, {
  style: (feature) => ({
    color: feature.properties.decision === "refused" ? "#b4342a" : "#2f6b45",
    weight: 2,
  }),
}).addTo(map);

That is one metered request per precedent, so plot the precedents a user actually asked to see rather than the whole result set. Pass geometry=none on detail requests where you need the record but not the shape.

#What the Boundary Does and Does Not Prove

The query tests each precedent's address point against your area. That point is the geocoded application address, and it is the least reliable field in the record: an application registered to one house number routinely sits tens of metres from its neighbour's pin, and authorities frequently geocode a large site to its road frontage.

So a boundary tells you precisely which site you mean. It is not proof of where the application was.

  • A hit is a strong positive signal. The point is inside your site.
  • A miss is inconclusive, not a rejection. A permission on your own site that the authority geocoded to the road would silently disappear.

Treat the boundary as a confirming signal alongside address and eircode identity, never as the sole filter. For a complete picture, run the boundary search and the address or eircode search at Site History, then merge the two result sets on precedent id.

#Errors

StatusMeaning
400geometry missing, malformed JSON body, wrong geometry type, a ring with fewer than four positions, or coordinates outside valid lon/lat range — the usual cause of the last being unprojected ITM or Irish Grid coordinates.
401Missing or invalid API key.
413Boundary area exceeds 5 km².
500Includes geometry that passes the checks above but that the spatial engine rejects — in practice, an unclosed ring. Close the ring and retry before treating it as an outage.

#Where It Fits

Use this instead of a radius whenever you hold a real site extent. Use Site History when all you have is an address or eircode, and Get Precedent Details to expand any id either one returns.