API
Find stations and retrieve their measurements, dates, units and sources.
Base URL https://semantic.construction/v1
Requests return data we have already collected. Some sources and periods are incomplete. The map and station report read this database. Earlier exports remain available as dated downloads.
Start with a site
Search by a place or station name. You can narrow the result with a provider identifier, such as ea_bathing, ea_wqe, ea_hydrology or uk_air.
curl 'https://semantic.construction/v1/sites?provider=ea_bathing&search=Kingston&limit=10'
- Take a site's
idfrom the returneditemslist. - Request
/sites/{id}/seriesto find its metrics (measurement series). - Use a series
idin/series/{id}/observationsto read its history. - Use the series
metric_idin/metrics/{metric_id}for its explanation and references.
Site and series IDs are opaque 32-character hexadecimal identifiers. Keep them unchanged. For an exact publisher identity, query sites with both provider and source_id; matching station names alone does not establish that two records describe the same site.
Available requests
All endpoints below use GET and return JSON. Prefix each path with /v1.
| Path | Parameters | Returns |
|---|---|---|
/reporting | None | Station feeds with measurements in the past 24 hours and seven days, event transitions, recent gaps and dated publisher statuses within 50 km of central London. |
/reporting/stations | None | Station reporting catalogue, classifications, source checks and an as_of cutoff. |
/reporting/stations/{id} | as_of from the catalogue | Station coverage, monthly records, reporting gaps and evidenced outage episodes. |
/map/observations | bbox, provider, limit, reported_since | A GeoJSON FeatureCollection containing the last stored observation per metric and archived overflow monitor states in the requested bounds, with result-limit metadata. |
/sites | provider, source_id, dataset, search, limit, after | Matching sites in items, in ID order. Pass next_cursor as after for another page. |
/sites/{id}/series | counts | The site's stored metrics in items, with definitions, units, coverage dates and the latest measurement. Counts are included unless counts=false. |
/sites/{id}/metadata | limit | Archived site and metric definitions in items, newest first, with site_id and truncated. |
/series/{id}/observations | from, to, limit, cursor, coverage | series, date range, observation items and optional next_cursor. Interval coverage is included unless coverage=false. |
/series/{id}/chart | from, to, points | A bounded chart: exact observations or time buckets, with the series, date range and coverage. |
/sites/{id}/events | from, to, limit, cursor | site_id, date range, event items and optional next_cursor. |
/context/series | lon, lat, radius_m, per_category, paired from/to | Nearby stored series in six categories, with station identities, dates, distances and category limits. |
/rivers/context | provider, source_id, upstream_m, downstream_m, per_category, paired from/to | Verified river-network associations, directed distances and qualifying stored series. |
/metrics/{metric_id} | None | An explanation, references and limitations for the source metric. |
/health | None | {"status":"ok"} when the service can reach its database. |
Recent reporting
The reporting counts are station feeds with measurements in each period, not numbers of measurements. They use observation dates, not download dates. Importing older history does not increase today's count. Each publisher's station ID counts as a separate feed; two feeds may describe the same physical station. Entries without a usable location are counted separately from the area totals.
Gaps in our archive do not establish that a station is offline. Publisher statuses retain their own check dates, and unavailable or old status information is kept separate. Periodic laboratory samples are not treated as silent sensors.
Map measurements
GET /v1/map/observations returns the last stored observation in each measurement series at located sites within a bounding box. It includes water, weather and UK-AIR measurements, plus the latest archived Thames overflow monitor states. Monitor states describe source reports; they are not numeric measurements.
curl 'https://semantic.construction/v1/map/observations?bbox=-0.5,51.3,0.2,51.7&provider=ea_bathing&limit=500'
bboxis required: four comma-separated WGS84 coordinates inwest,south,east,northorder, with longitude first. The box must not wrap across the antimeridian.provideris optional. Accepted values areea_wqe,ea_hydrology,ea_flood,ea_bathing,metoffice_land,uk_airandthames_overflows. Omit it to include all sources.limitdefaults to 500 features and allows at most 1,000. A feature represents one metric or one monitor report. The all-source view interleaves providers and stations before additional channels at the same station; choosing one provider uses station-name order.reported_sinceis an optional RFC3339 timestamp. It selects stations with a measurement between that time and query time, before applying the result limit. Overflow monitors use the latest archived status-check time. All metrics at qualifying stations remain available, including older measurements. Future-dated readings do not qualify a station.
The GeoJSON response has Point features with flat properties. Measurement features carry a series_id; monitor features carry their site_id. These exact IDs connect to measurement history or archived site definitions. Similar site names are not joined across sources.
- Identity:
provider,station_id(publisher site identifier),measure_id(publisher series identifier),source_observation_id,name,parameter,metric_idandkind. - Result and timing:
value,raw_value,unit,qualifier,quality,observed_at, optionalinterval_end,source_timeandtime_basis. - Method and source:
matrix,method,statistic,period_seconds,datum, optionalinterpretation_pending,source_url,retrieved_atandarchive_id.
Alongside type and features, the response has top-level queried_at, returned, truncated, limit, bbox and provider. The returned bbox is an array in the same coordinate order; an empty provider string means all supported sources. The query timestamp describes when the database was read; each observation retains its own date. When truncated is true, more series matched than were returned. Narrow the box or choose a provider. This bounded map result has no continuation cursor.
A last stored observation can be old or nonnumeric; a missing numeric value stays null. The latest record is selected by its observation date, without falling back to an older numeric value. The endpoint includes stored observations and archived monitor reports at located sites. It does not describe present conditions, return a complete year per series or measure total agency coverage. Date-range and text-search filters are not supported on this route.
Weather results retain their source QC JSON, averaging or accumulation period and timestamps. Air and water temperatures, rainfall totals and rainfall rates remain distinct. A series with interpretation_pending: true remains available in history but is excluded from numeric colour scales.
On the map, use Search this view after moving or zooming. Opening the map or changing its provider filter makes one request. Panning does not automatically load more records, and there is no automatic polling. These requests read stored data only.
Only stations reporting in the last 5 days applies this station filter. Turning it off restores older stations. The separate source-date filter still applies to individual displayed measurements.
Dates and pages
Without date parameters, observation, event and chart requests cover the previous 365 complete UTC days. from is inclusive; to is exclusive. Context discovery searches all retained history when dates are omitted.
Use YYYY-MM-DD for UTC midnight, or an RFC3339 timestamp with an explicit offset. For example, from=2026-09-01&to=2026-09-02 selects one complete UTC day. URL-encode parameter values, including a + in a timezone offset.
Pages default to 500 records. Observation and event pages allow up to 10,000; site lists allow up to 1,000. When a response has next_cursor, send it unchanged as cursor with the same endpoint and date range.
counts=false on station-series requests keeps first/last dates and the latest measurement, but omits coverage.count. coverage=false on observation requests omits the whole interval coverage object. Use it on later pages to avoid repeating an exact count.
Site pages use after: pass the returned next_cursor unchanged. An empty result is items: [].
Interpreting results
Measurements retain value, raw_value, qualifier, quality flags, source dates and provenance. A missing numeric value is null, never an implied zero. A result below a detection limit is not an exact concentration.
Display units and the source's time_basis. A UTC date selector does not redefine an instrument's sampling period or resolve an undocumented publisher timezone.
coverage reports stored counts and dates; it does not promise continuous monitoring. An event without an end can be a transition point, not an ongoing incident. Global earthquake and fire catalogues retain their original geographic scope.
Site and series metadata marked interpretation_partial lists unknown_fields that have not yet been interpreted. Recognized fields remain available; their presence does not mean every source field was understood.
Scientific references can depend on averaging periods, river types or other conditions. They are not automatic safety bands for individual samples. Preserve source attribution and check the source licences.
Charts
/series/{id}/chart?from=2026-09-01&to=2026-09-02&points=1000 covers the selected interval with a bounded number of marks. points defaults to 1,000 and allows 32–2,000; chart ranges allow at most 366 days.
mode: "observations" returns exact sparse measurements in items. Denser results use mode: "time-buckets" with at most floor(points / 4) buckets. Each bucket retains actual first, last, eligible minimum and eligible maximum measurements, including their timestamps and provenance. No means, interpolation or predictions are supplied; joining these selected marks would hide omitted measurements.
Buckets include empty intervals, total and eligible counts, flagged, censored and missing counts, and max_gap_seconds. Counts can overlap. The largest gap measures intervals between stored observations; it does not establish why a source was absent. Raw observations remain separately available through the paginated observations route.
Nearby and river context
/context/series requires WGS84 lon and lat. radius_m defaults to 10,000 metres and allows more than zero through 50,000. It returns tidal level, water level, water temperature, air temperature, rainfall and river flow categories. Each item keeps its site and series identities, distance and first/last stored dates.
/rivers/context requires the station's provider and source_id. Upstream and downstream distances each default to 10,000 metres and allow 0–50,000. Associations follow the configured directed river network, with its version and binding evidence. An unknown association returns associated: false. Geographic proximity alone does not establish river connectivity, and mapped direction does not describe instantaneous tidal current.
Both routes accept paired from and to parameters. With dates, candidates must have an actual stored observation in that interval, and returned first/last dates refer to that interval. Without dates, discovery uses all retained history. per_category defaults to 3 and allows 1–20 series. One channel from each station ranks before further channels; category truncated flags indicate omitted candidates. River stations can include associations without stored measurements; categories requires observations.
Station definitions
/sites/{id}/metadata?limit=100 returns retained site and series definitions, newest first. limit defaults to 100 and allows 1–500. The result has site_id, items and truncated; there is no continuation cursor.
Each item identifies the entity, archive, parser version, retrieval time, origin and original definition record. Repeated definitions can appear. retrieved_at is the archive time, not necessarily when a change happened. Records with origin: "merged_baseline" retain an earlier combined definition and cannot establish a historical transition. The route does not infer equivalent stations or causes of outages.
Caching and saved results
Successful data responses use Cache-Control: public, max-age=15, must-revalidate. The reporting summary uses max-age=120; metric definitions use max-age=3600. Both supply an ETag; send it as If-None-Match to allow a 304 Not Modified response when the response bytes match. Responses containing query timestamps can change ETag even when measurements are unchanged. Health and errors use no-store.
Each SQL query uses a consistent database snapshot. Separate pages can reflect later imports or corrections; a cursor does not freeze a database edition. Save the returned data and provenance when an analysis must be reproducible. The station report shares a cutoff between its catalogue and station details. Source checks and measurement times remain separate.
Errors
Errors return JSON with a diagnostic code and message:
{"error":{"code":2004,"message":"Invalid query."}}
HTTP 400 means invalid parameters or cursor; 404 means no matching record. 429 means the request rate is too high. 503 indicates temporary service or storage unavailability, and 504 indicates a query timeout. Hosting errors may use a different response format. Correct invalid requests before resubmitting. For temporary errors, wait before retrying and use bounded retries rather than repeated immediate requests.