Open data API
Hourly counts of what people report about their tap water supply, by area. Version 1.0.0, CC BY 4.0.
Start
Voda is a map where people report whether they have tap water right now. This API publishes those reports as counts per H3 area and UTC hour. Never a single report, a position, a device or an address.
An area-hour is published only when it holds at least 3 reports. The rest are counted in
their region's n_suppressed. Counts are what people said, not measurements: see the methodology.
No key or account. A day is settled 48 hours after its end: its counts then rarely change, and it may be cached for a day. Cite as: Voda (voda.earth), CC BY 4.0, accessed YYYY-MM-DD.
Base URL https://app.voda.earth/api/data/v1. The contract: openapi.json (OpenAPI 3.1.0). How the counts come about: the data page.
Endpoints
GET /
The dataset: coverage, license, how to cite, and links. Includes a schema.org Dataset (JSON-LD) for data portals.
GET /openapi.json
This document.
GET /days/{day}.{format}
Every published area-hour of one UTC day. Ordered by hour, then area. Today holds the full hours so far.
| Parameter | In | Type | |
|---|---|---|---|
day required | path | string (date) | |
format required | path | json | geojson | csv | |
limit | query | integer 1–5000 | Default 5000. |
cursor | query | string | From the previous page's next. Opaque. |
GET /observations
Area-hours of a time range in one place. Exactly one of cell, parent, bbox or place. At most 168 hours.
| Parameter | In | Type | |
|---|---|---|---|
from required | query | string (date-time) | First hour, included. ISO 8601 UTC, a whole hour. |
to required | query | string (date-time) | Last hour, excluded. At most 168 hours after from. |
cell | query | string | One area (res 9 or 10). A res-9 area also returns the small areas inside it. |
parent | query | string | Every area of one res-5 region. |
bbox | query | string | west,south,east,north in degrees. At most 8 res-5 regions. |
place | query | string | A place's id, as in /api/places: its box. |
format | query | json | geojson | csv | Default json. |
limit | query | integer 1–5000 | Default 5000. |
cursor | query | string | From the previous page's next. Opaque. |
GET /regions
Every region-hour of a time range, with what was suppressed.
| Parameter | In | Type | |
|---|---|---|---|
from required | query | string (date-time) | First hour, included. ISO 8601 UTC, a whole hour. |
to required | query | string (date-time) | Last hour, excluded. At most 168 hours after from. |
format | query | json | geojson | csv | Default json. |
limit | query | integer 1–5000 | Default 5000. |
cursor | query | string | From the previous page's next. Opaque. |
Rows
Observation: one area, one hour
One area in one hour. Published only with at least 3 reports.
| Column | Type | |
|---|---|---|
hour_utc | string (date-time) | Start of the hour, ISO 8601 UTC. The hour runs to the next one. |
h3_cell | string | H3 index (https://h3geo.org). |
h3_res | 9 | 10 | 10: a small area (~135 m across). 9: a big area (~360 m), reports before 2026-09-27. |
center_lat | number | Latitude of the hexagon's centre, WGS 84, 5 decimals. Never a report's position. |
center_lng | number | Longitude of the hexagon's centre, WGS 84, 5 decimals. Never a report's position. |
n_reports | integer | Reports in this hour: n_good + n_ok + n_low + n_none. |
n_good | integer | Water, strong pressure. |
n_ok | integer | Water, medium pressure. |
n_low | integer | Water, weak pressure. |
n_none | integer | No water. |
n_out_lt1h | integer | Of n_none: without water for less than an hour. |
n_out_h1_6 | integer | Of n_none: for 1 to 6 hours. |
n_out_h6_24 | integer | Of n_none: for 6 to 24 hours. |
n_out_gt24h | integer | Of n_none: for more than 24 hours. |
Region: one res-5 region, one hour
One region (H3 res 5, ~20 km across) in one hour: every report in it.
| Column | Type | |
|---|---|---|
hour_utc | string (date-time) | Start of the hour, ISO 8601 UTC. The hour runs to the next one. |
h3_cell | string | H3 index (https://h3geo.org). |
h3_res | 5 | |
center_lat | number | Latitude of the hexagon's centre, WGS 84, 5 decimals. Never a report's position. |
center_lng | number | Longitude of the hexagon's centre, WGS 84, 5 decimals. Never a report's position. |
n_reports | integer | Reports in this hour: n_good + n_ok + n_low + n_none. |
n_good | integer | Water, strong pressure. |
n_ok | integer | Water, medium pressure. |
n_low | integer | Water, weak pressure. |
n_none | integer | No water. |
n_suppressed | integer | Of n_reports: those in area-hours with fewer than 3 reports, not published by area. |
Formats and pages
JSON {"data": [...], "next": url | null}; GeoJSON, a FeatureCollection with one hexagon Polygon per row; CSV with a header line, CRLF. At most 5,000 rows a page: follow next, also sent as Link: <…>; rel="next".
Errors
RFC 9457 problem details, application/problem+json, with type, title, status, detail and a stable error code:
invalid: The request is not valid (400)range_too_long: The time range is longer than 168 hours (400)area_too_large: The area covers more than 8 regions (400)bad_cursor: The cursor is not valid for this query (400)not_found: Not found (404)internal: Internal error (500)
Caching and versions
A day or range that ended more than 48 hours ago is settled: Cache-Control: public, max-age=86400. Its counts rarely change after that: only when a person withdraws an answer of a report still open, or a report that was not real is removed. Anything newer: 5 minutes. Every data answer has an ETag (marked weak, W/, when it comes compressed); send it back as If-None-Match for a 304. Any web page may read the API (Access-Control-Allow-Origin: *).
The version is in the path. Version 1 is frozen: a new column, a changed meaning or a changed threshold makes /v2, and /v1 keeps working. A version that will stop is announced at least six months ahead with the Deprecation and Sunset headers.
Cite as: Voda (voda.earth), CC BY 4.0, accessed YYYY-MM-DD.