---
title: "List Geographies"
method: GET
path: "/api/v1/geographies"
tags: ["geographies"]
---

# List Geographies

`GET /api/v1/geographies`

## Request

- Base URL: `https://statsmapped.com`
- URL: `https://statsmapped.com/api/v1/geographies`
- Auth: none declared

Areas StatsMapped knows about.

`with_boundary=true` returns GeoJSON geometry — off by default because the raw
boundaries are heavy: Tailte's Generalised-20m layers still carry full coastline and
island detail, so the 8 CSO regions alone are **3.2 MB** unsimplified.

`simplify` is a Douglas-Peucker tolerance in degrees, applied server-side via
ST_SimplifyPreserveTopology. The default 0.005 (~500 m) is far below what is visible
on a map of Ireland a few hundred pixels wide, and cuts the payload by ~95%. Pass
`simplify=0` for the exact boundary.

Caveat worth knowing: simplification is per-geometry, so adjacent areas can develop
hairline gaps at shared borders. Acceptable for a choropleth at this scale; use
simplify=0 if the geometry is ever needed for real spatial analysis rather than
display.

`simplify` and `min_part_km2` are capped (0.1 degrees, 1000 km²) rather than left
unbounded — the front end only ever requests the defaults, and an uncapped value on
either param is a resource-cost knob a caller could otherwise turn arbitrarily far in
either direction on a shared free-tier database (security-architecture-and-audit.md
§4). 0 remains a valid floor for both so the documented "exact boundary" escape hatch
for real spatial analysis still works; the caps only stop nonsensical/extreme inputs.

`level` is required when `with_boundary=true` (cycle 45, Option B of the
security-privacy-auditor's 2026-08-27 open-routes finding: `with_boundary=true`
with no level filter had no row cap against a 4-connection pool -- one request
could ask for the full boundary set, every level at once, at full resolution).
`level` stays optional without `with_boundary` -- app.js's own bare
`fetch('/api/geographies')` (state.geoIndex at init()) needs the full unfiltered
index and requests no geometry, so it carries none of the cost this guards
against. Checked live: every existing with_boundary=true call site (map.js's two)
already passes level, so this tightens the contract without breaking a real caller.

## Query parameters

- `level` string, nullable
- `with_boundary` boolean
- `simplify` number
- `min_part_km2` number

## Response `200`

Successful Response

- GeographyEntry[]
  - `country` string
  - `id` string, required
  - `level` string, required
  - `name` string, required
  - `name_ga` string, nullable
  - `publishable` boolean, nullable
  - `vintage` string, nullable
  - `source_note` string, nullable
  - `boundary` object, nullable
  - `centroid` object, nullable
  - `parents` GeographyParent[]
    - `country` string
    - `id` string, required
    - `level` string, required
    - `name` string, required
    - `relation` string, required
  - `slug` string, nullable

## Other responses

- `400`: Bad request
- `401`: Missing or invalid credentials
- `403`: Not authorized for this request
- `404`: Not found
- `422`: Validation error
- `429`: Rate limited

---

[API](https://skmtc.dev/statsmapped/apis/statsmapped-api.md) · [All operations](https://skmtc.dev/statsmapped/apis/statsmapped-api/llms.txt) · [OpenAPI document](https://skmtc.dev/statsmapped/apis/statsmapped-api/revisions/7c5b264d05b7?raw)
