Data APIdocs

Population & areas

Every res-9 H3 cell carries a population count, the OSM admin breadcrumb that locates it (United Kingdom → England → Greater London → Camden → …), and — where a country publishes small-area census — a demographics block. This is the data-layer side of the H3 Data API: it answers "how many people live here, and who are they?".

Two companion pieces sit alongside it:

  • Transit coverage (/v1/h3/transit-coverage) — the share of a cell's population within a 15-minute walk of a public-transport station, and the same rolled up per admin area.
  • Areas (/v1/divisions) — the administrative-boundary hierarchy. You browse the tree and ask any division for its H3 cells, then feed those cells back into any H3 layer.

Where the numbers come from

  • Population is WorldPop (2020, 100 m grid) summed into each res-9 cell. It exists for every inhabited cell, everywhere we ship a country.
  • Demographics come from each country's official small-area census and small-area income product (for GB: ONS Census 2021 output areas + modelled MSOA net household income). A cell's rates are the population-correct apportionment of the source units that overlap it. Cells with no census coverage carry population but a null demographics block — never a fabricated zero.

Querying the population layer

GET /v1/h3/population uses the same geographic-input contract as the rest of /v1/h3/* — supply exactly one of:

  • bbox=south,west,north,east — every indexed cell whose centroid falls inside the box. Add min_population=1 to drop empty cells (the common case for a viewport heatmap).
  • cells=<csv> — look up a specific set of cells. A single id is just a one-element list; an unindexed cell is simply absent from the result (no 404).
  • division=<id> — every cell inside an admin division. The id comes from the Areas API (/v1/divisions).
# Population for the cells of a London viewport
curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/h3/population?country=GB&bbox=51.50,-0.15,51.55,-0.10&min_population=1"

# Everyone inside a division (Areas API id)
curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/h3/population?country=GB&division=R51800"

Every response is the shared H3 envelope: { country, resolution, count, limit, truncated, generated_at, cells }. limit caps at 10,000 cells; add fields=slim to trim each row to h3 + population.

Rolling an area up

To get one area-level summary instead of per-cell rows — population sums plus population-weighted mean rates — POST the cell-set to the aggregate action. This is what powers catchment analytics: a travel-time isochrone, a drawn polygon, or a division all reduce to a set of H3 cells.

Send exactly one area: cells (up to 100,000 ids, read in chunks server-side) or division_id. Use the id for anything county-scale — the footprint is resolved server-side, so you never hit the 10,000-cell truncation on GET /v1/divisions/{id}/cells.

curl -X POST -H "Authorization: Bearer $POI_API_KEY" \
  -H 'content-type: application/json' \
  "$POI_BASE/v1/h3/population/aggregate?country=GB" \
  -d '{"cells": ["89195da4a93ffff", "89195da4a97ffff"]}'

# A whole division, without shipping its cells
curl -X POST -H "Authorization: Bearer $POI_API_KEY" \
  -H 'content-type: application/json' \
  "$POI_BASE/v1/h3/population/aggregate?country=GB" \
  -d '{"division_id": "R51800"}'

Transit coverage

GET /v1/h3/transit-coverage returns the covered cells (those within a 15-min walk of a station) for a bbox, cells, or division, with their covered population, station/line counts, and serving modes. The per-area headline — coverage % of a division's population — is at /v1/h3/transit-coverage/divisions.

curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/h3/transit-coverage?country=GB&bbox=51.48,-0.20,51.56,-0.05&res=9"

To draw the gap rather than infer it, add include_uncovered=true: populated cells no station reaches come back with covered: false and their uncovered_pop, and at res=6/7 every parent hex carries the summed uncovered population beneath it — the country-zoom view, which no join against the res-9 population layer can produce. limit defaults to the 10,000 cap under include_uncovered; at res=7 a whole-country box still holds more populated parent hexes than that, so it comes back truncated: true — narrow the box; res=6 fits in one page.

curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/h3/transit-coverage?country=GB&bbox=50.0,-6.0,58.7,1.8&res=7&include_uncovered=true"

Areas (the boundary hierarchy)

/v1/divisions is a tree of administrative polygons, not a per-cell layer — so it lives next to the H3 layers rather than under /v1/h3. Browse it (/v1/divisions, /{id}, /{id}/children) and ask any division for its cells (/v1/divisions/{id}/cells), then pass those cells into the population, transit-coverage, POI, or land-cover layers.