From 07b8069d6d8d87a26710cb38abd9b53423eb7d41 Mon Sep 17 00:00:00 2001 From: baalmart Date: Sun, 7 Jun 2026 23:32:52 +0300 Subject: [PATCH 1/3] Update API docs: forecasts and self-contained metadata reference --- src/docs-website/docs/api/for-cities/intro.md | 9 +- .../docs/api/for-partners/intro.md | 9 +- .../docs/api/forecasts/overview.md | 420 +++++++++------- .../docs/api/reference/error-codes.md | 20 +- .../docs/api/reference/finding-ids.md | 33 +- .../docs/api/reference/metadata.md | 448 ++++++++++++++++++ .../docs/api/reference/response-structure.md | 44 +- 7 files changed, 774 insertions(+), 209 deletions(-) create mode 100644 src/docs-website/docs/api/reference/metadata.md diff --git a/src/docs-website/docs/api/for-cities/intro.md b/src/docs-website/docs/api/for-cities/intro.md index 7a58240d4b..a870eb8d17 100644 --- a/src/docs-website/docs/api/for-cities/intro.md +++ b/src/docs-website/docs/api/for-cities/intro.md @@ -41,13 +41,15 @@ If a device belongs to an organisation's private Cohort, it will **not** appear | Historical measurements | `POST /api/v3/public/analytics/data-download` | | Spatial heatmap (city) | `GET /api/v2/spatial/heatmaps/{GRID_ID}?token={SECRET_TOKEN}` | | All available heatmaps | `GET /api/v2/spatial/heatmaps?token={SECRET_TOKEN}` | +| Daily forecasts | `GET /api/v2/predict/daily-forecasting/?grid_id={GRID_ID}&token={SECRET_TOKEN}` | +| Hourly forecasts | `GET /api/v2/predict/hourly-forecasting/?grid_id={GRID_ID}&token={SECRET_TOKEN}` | --- ## Finding your Grid ID -- **Browse the metadata endpoints** at [docs.airqo.net/airqo-rest-api-documentation/api-endpoints/metadata](https://docs.airqo.net/airqo-rest-api-documentation/api-endpoints/metadata) -- **Filter by location** using the `admin_level` query parameter (country, province, or city) +- **Browse the [Metadata API](../reference/metadata.md#grid-summary-with-site-details):** call `GET /api/v2/devices/grids/summary?admin_level=city` to list all city-level grids with their site details +- **Filter by location** using the `admin_level` query parameter (`country`, `province`, `city`, `district`) - **Example:** The Grid ID for Nairobi is `64b7ac8fd7249f0029feca80` --- @@ -60,7 +62,7 @@ If a device belongs to an organisation's private Cohort, it will **not** appear | Spatial heatmaps | ✅ Free | Base64 PNG with boundary coordinates | | Historical calibrated data | ✅ Standard+ | Via Analytics API | | Raw sensor readings | ✅ Standard+ | Via Analytics API | -| Air quality forecasts | ✅ Premium | Via site or device ID from your Grid | +| Air quality forecasts | ✅ Premium | Via `grid_id` or per-site `site_id` — see [Forecast API](../forecasts/overview.md) | --- @@ -79,4 +81,5 @@ City authorities already using Grid ID access: - [Fetch recent measurements →](./recent-measurements.md) - [Access historical data →](./historical-data.md) - [Generate spatial heatmaps →](./spatial-heatmaps.md) +- [Get air quality forecasts →](../forecasts/overview.md) - [Set up your account →](../getting-started/authentication.md) diff --git a/src/docs-website/docs/api/for-partners/intro.md b/src/docs-website/docs/api/for-partners/intro.md index bdd89c8146..570478ab68 100644 --- a/src/docs-website/docs/api/for-partners/intro.md +++ b/src/docs-website/docs/api/for-partners/intro.md @@ -33,12 +33,14 @@ At this time, Cohorts are created and configured by the AirQo team on behalf of ## Your Cohort ID endpoint structure -Once you have your Cohort ID, two endpoints cover your primary data needs: +Once you have your Cohort ID, these endpoints cover your primary data needs: | Data | Endpoint | |------|----------| | Recent measurements (last 7 days) | `GET /api/v2/devices/measurements/cohorts/{COHORT_ID}?token={SECRET_TOKEN}` | | Historical measurements | `POST /api/v3/public/analytics/data-download` | +| Daily forecasts | `GET /api/v2/predict/daily-forecasting/?cohort_id={COHORT_ID}&token={SECRET_TOKEN}` | +| Hourly forecasts | `GET /api/v2/predict/hourly-forecasting/?cohort_id={COHORT_ID}&token={SECRET_TOKEN}` | --- @@ -52,7 +54,7 @@ To change the privacy setting of your Cohort, contact [support@airqo.net](mailto ## Getting your Cohort ID -- **Public Cohorts:** Browse the metadata endpoints at [docs.airqo.net/airqo-rest-api-documentation/api-endpoints/metadata](https://docs.airqo.net/airqo-rest-api-documentation/api-endpoints/metadata) +- **Public Cohorts:** Use the [Metadata API](../reference/metadata.md#list-all-public-cohorts) — call `GET /api/v2/devices/metadata/cohorts` to list all publicly visible cohorts - **Private Cohorts:** Contact [support@airqo.net](mailto:support@airqo.net) — your Cohort ID will be shared securely --- @@ -64,7 +66,7 @@ To change the privacy setting of your Cohort, contact [support@airqo.net](mailto | Recent hourly measurements | ✅ Free | Last 7 days | | Historical calibrated data | ✅ Standard+ | Via Analytics API | | Raw sensor readings | ✅ Standard+ | Via Analytics API | -| Air quality forecasts | ❌ | Not currently available per Cohort ID | +| Air quality forecasts | ✅ Premium | Via `cohort_id` on the [Forecast API](../forecasts/overview.md) | | Spatial heatmaps | ❌ | Heatmaps use Grid ID | --- @@ -73,4 +75,5 @@ To change the privacy setting of your Cohort, contact [support@airqo.net](mailto - [Fetch recent measurements →](./recent-measurements.md) - [Access historical data →](./historical-data.md) +- [Get air quality forecasts →](../forecasts/overview.md) - [Set up your account →](../getting-started/authentication.md) diff --git a/src/docs-website/docs/api/forecasts/overview.md b/src/docs-website/docs/api/forecasts/overview.md index d89711bc38..7a9b55064e 100644 --- a/src/docs-website/docs/api/forecasts/overview.md +++ b/src/docs-website/docs/api/forecasts/overview.md @@ -5,7 +5,7 @@ sidebar_label: Forecast API # Forecast API -Get hourly and daily air quality predictions for up to 7 days ahead, with health recommendations based on predicted pollution levels. +The Predict API provides site-level PM2.5 air quality forecasts generated by AirQo forecasting models. The service supports both daily forecasts (7-day horizon) and hourly forecasts (up to 240 hours ahead by default). Forecasts can be retrieved for individual monitoring sites, grids, or cohorts of monitoring sites. :::info Tier requirement Forecasts require a **Premium Tier** subscription. @@ -13,242 +13,306 @@ Forecasts require a **Premium Tier** subscription. --- -## What you get - -- **7-day hourly forecasts** — one prediction per hour for the next 168 hours -- **7-day daily forecasts** — one prediction per day for the next 7 days -- **Health tips** — contextual recommendations based on predicted PM2.5 levels -- **Forecast metadata** — model version, generation timestamp, and location details - -Forecasts are available for any active monitoring site or device. They are regenerated every 6 hours (00:00, 06:00, 12:00, 18:00 UTC). - ---- - -## Endpoints - -### Hourly forecasts - -#### By Device ID +## Daily Forecasting Endpoint ``` -GET https://api.airqo.net/api/v2/predict/hourly-forecast?device_id={DEVICE_ID}&token={SECRET_TOKEN} +GET /api/v2/predict/daily-forecasting/ ``` -#### By Site ID +Returns PM2.5 forecasts for the next 7 days, grouped by monitoring site. -``` -GET https://api.airqo.net/api/v2/predict/hourly-forecast?site_id={SITE_ID}&token={SECRET_TOKEN} -``` +### Supported Parameters -### Daily forecasts +| Parameter | Description | +|-----------|-------------| +| `site_id` | Forecast for a single site | +| `grid_id` | Forecasts for all sites within a grid | +| `cohort_id` | Forecasts for all sites within a cohort | +| `scope` | Optional — `"grid"` or `"cohort"` to disambiguate when a scope ID could match either type | -#### By Device ID +Only one of `site_id`, `grid_id`, or `cohort_id` may be provided per request. -``` -GET https://api.airqo.net/api/v2/predict/daily-forecast?device_id={DEVICE_ID}&token={SECRET_TOKEN} -``` +### Response Structure -#### By Site ID - -``` -GET https://api.airqo.net/api/v2/predict/daily-forecast?site_id={SITE_ID}&token={SECRET_TOKEN} +```json +{ + "success": true, + "data": { + "start_date": "2026-06-03", + "end_date": "2026-06-09", + "days": 7, + "total": 1, + "units": { + "pm2_5": "ug/m3", + "air_temperature": "degC", + "relative_humidity": "%", + "air_pressure_at_sea_level": "hPa", + "precipitation_amount": "mm", + "cloud_area_fraction": "%", + "wind_speed": "m/s", + "wind_from_direction": "degrees", + "forecast_confidence": "%" + }, + "forecasts": [ + { + "site_details": { + "site_id": "site-123", + "site_name": "Kampala Central", + "site_latitude": 0.3476, + "site_longitude": 32.5825 + }, + "forecasts": [ + { + "date": "2026-06-03", + "forecast": { + "pm2_5_mean": 28.4, + "pm2_5_low": 22.1, + "pm2_5_high": 34.8, + "pm2_5_min": 19.0, + "pm2_5_max": 37.2, + "forecast_confidence": 84.5 + }, + "aqi": { + "category": "Moderate", + "label": "Air quality is acceptable for most people.", + "trend_message": "Air pollution may increase over the next 24 hours." + }, + "met": { + "air_temperature": 24.7, + "relative_humidity": 72.1, + "air_pressure_at_sea_level": 1012.3, + "precipitation_amount": 0.0, + "cloud_area_fraction": 45.0, + "wind_speed": 3.2, + "wind_from_direction": "NE" + } + } + ] + } + ] + } +} ``` --- -## Query parameters +## Hourly Forecasting Endpoint -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `device_id` | string | Conditional | Device identifier (use either `device_id` or `site_id`) | -| `site_id` | string | Conditional | Site identifier (use either `site_id` or `device_id`) | -| `token` | string | Yes | Your SECRET TOKEN | +``` +GET /api/v2/predict/hourly-forecasting/ +``` ---- +Returns hourly PM2.5 forecasts beginning from the current forecast hour. -## Example requests +### Supported Parameters -```bash -# Hourly forecast by site ID -curl -X GET \ - "https://api.airqo.net/api/v2/predict/hourly-forecast?site_id=64f7b3e8c9d25a0013f2d456&token=YOUR_SECRET_TOKEN" +| Parameter | Type | Description | +|-----------|------|-------------| +| `site_id` | string | Forecast for a single site | +| `grid_id` | string | Forecasts for all sites within a grid | +| `cohort_id` | string | Forecasts for all sites within a cohort | +| `scope` | string | Optional — `"grid"` or `"cohort"` to disambiguate | +| `page` | integer | Page number (default: `1`) | +| `limit` | integer | Results per page (default: `10`, max: `100`) | -# Daily forecast by device ID -curl -X GET \ - "https://api.airqo.net/api/v2/predict/daily-forecast?device_id=65c8d4a2f1b45c0012a3e789&token=YOUR_SECRET_TOKEN" -``` +Only one of `site_id`, `grid_id`, or `cohort_id` may be provided per request. -**JavaScript — hourly forecast:** +### Key Features -```js -async function getHourlyForecast(siteId, token) { - const url = `https://api.airqo.net/api/v2/predict/hourly-forecast?site_id=${siteId}&token=${token}`; - const response = await fetch(url); - const data = await response.json(); +- Default forecast horizon: **240 hours (10 days)** +- Pagination support across all sites in a grid or cohort +- AQI category, advisory label, and trend message included +- Uncertainty range via `pm2_5_q10` and `pm2_5_q90` percentiles +- Meteorological context included per hour - if (data.success) { - console.log(`Retrieved ${data.forecasts.length} hourly forecasts`); +### Response Structure - // Show next 24 hours - data.forecasts.slice(0, 24).forEach(forecast => { - console.log(`${forecast.time}: PM2.5 = ${forecast.pm2_5} μg/m³`); - console.log(`Tips: ${forecast.health_tips.join(' | ')}`); - }); +```json +{ + "success": true, + "data": { + "start_timestamp": "2026-06-07T10:00:00+00:00", + "end_timestamp": "2026-06-17T10:00:00+00:00", + "hours": 240, + "total": 10, + "page": 1, + "pages": 24, + "units": { + "pm2_5": "ug/m3", + "air_temperature": "degC", + "relative_humidity": "%", + "air_pressure_at_sea_level": "hPa", + "precipitation_amount": "mm", + "cloud_area_fraction": "%", + "wind_speed": "m/s", + "wind_from_direction": "degrees", + "forecast_confidence": "%" + }, + "forecasts": [ + { + "site_details": { + "site_id": "site-123", + "site_name": "Kampala Central", + "site_latitude": 0.3476, + "site_longitude": 32.5825 + }, + "forecasts": [ + { + "timestamp": "2026-06-07T10:00:00+00:00", + "forecast": { + "pm2_5_mean": 28.4, + "pm2_5_q10": 20.1, + "pm2_5_q90": 36.2, + "forecast_confidence": 84.5 + }, + "aqi": { + "category": "Moderate", + "label": "Air quality is acceptable for most people.", + "trend_message": "Air pollution may increase over the next hour." + }, + "met": { + "air_temperature": 24.7, + "relative_humidity": 72.1, + "air_pressure_at_sea_level": 1012.3, + "precipitation_amount": 0.0, + "cloud_area_fraction": 45.0, + "wind_speed": 3.2, + "wind_from_direction": "NE" + } + } + ] + } + ] } - - return data; } - -getHourlyForecast('64f7b3e8c9d25a0013f2d456', 'YOUR_SECRET_TOKEN'); ``` -**Python — daily forecast:** - -```python -import requests -from datetime import datetime - -def get_daily_forecast(device_id, token): - response = requests.get( - "https://api.airqo.net/api/v2/predict/daily-forecast", - params={"device_id": device_id, "token": token} - ) - data = response.json() +--- - if data['success']: - for forecast in data['forecasts']: - date = datetime.fromisoformat(forecast['time'].replace('+00:00', '')) - print(f"\n{date.strftime('%Y-%m-%d')}: PM2.5 = {forecast['pm2_5']} μg/m³") - for tip in forecast['health_tips']: - print(f" • {tip}") +## Scope (Grid and Cohort) Forecasting - return data +When grid-based or cohort-based forecasting is requested, the API supports aggregated forecast retrieval through: -get_daily_forecast('65c8d4a2f1b45c0012a3e789', 'YOUR_SECRET_TOKEN') +``` +GET /api/v2/predict/hourly-forecasting/ +GET /api/v2/predict/daily-forecasting/ ``` ---- +The `` path parameter accepts a grid ID or cohort ID. Use the optional `scope` query parameter (`"grid"` or `"cohort"`) if the ID is ambiguous. -## Example response +### Scope Metadata Returned ```json { - "success": true, - "message": "Forecasts retrieved successfully", + "scope": { + "type": "grid", + "id": "grid-123", + "grid_id": "grid-123" + }, + "sites_count": 3, + "sites_with_forecasts_count": 2, + "site_names": [ + "Kampala Central", + "Makerere" + ], "forecasts": [ { - "time": "2025-09-29T14:00:00+00:00", - "pm2_5": 24.5, - "health_tips": [ - "Air quality is acceptable for most people", - "Unusually sensitive individuals should consider reducing prolonged outdoor exertion" - ] - }, - { - "time": "2025-09-29T15:00:00+00:00", - "pm2_5": 28.3, - "health_tips": [ - "Air quality is acceptable for most people", - "Consider reducing outdoor activities if you experience symptoms" + "site_details": { + "site_id": "68fca1da53b8b4001372887f", + "site_name": "Buyala 1 Mpigi", + "site_latitude": 0.313465, + "site_longitude": 32.405833 + }, + "start_date": "2026-06-03", + "end_date": "2026-06-09", + "days": 7, + "total": 7, + "forecasts": [ + { + "date": "2026-06-03", + "forecast": { + "pm2_5_mean": 24.1, + "pm2_5_low": 18.0, + "pm2_5_high": 29.5 + } + } ] } - ], - "forecast_metadata": { - "model_version": "v2.3", - "generated_at": "2025-09-29T12:00:00+00:00", - "location": { - "site_id": "64f7b3e8c9d25a0013f2d456", - "device_id": "65c8d4a2f1b45c0012a3e789", - "latitude": 0.3476, - "longitude": 32.5825 - }, - "forecast_horizon_hours": 168 - } + ] } ``` --- -## Response fields - -### Forecast object - -| Field | Description | -|-------|-------------| -| `time` | Forecast timestamp (ISO 8601 with timezone) | -| `pm2_5` | Predicted PM2.5 in μg/m³ | -| `health_tips` | Array of health recommendations for the predicted air quality level | - -### `forecast_metadata` object - -| Field | Description | -|-------|-------------| -| `model_version` | Version of the forecasting model | -| `generated_at` | When this forecast was created | -| `location.site_id` | Site the forecast applies to | -| `location.device_id` | Device the forecast applies to | -| `forecast_horizon_hours` | Total hours covered (168 = 7 days) | +## Response Field Reference + +### Daily forecast item + +| Field | Type | Description | +|-------|------|-------------| +| `date` | string | Forecast date (ISO 8601 date) | +| `forecast.pm2_5_mean` | number | Mean PM2.5 forecast in µg/m³ | +| `forecast.pm2_5_low` | number | Lower bound of forecast range | +| `forecast.pm2_5_high` | number | Upper bound of forecast range | +| `forecast.pm2_5_min` | number | Minimum predicted value across the day | +| `forecast.pm2_5_max` | number | Maximum predicted value across the day | +| `forecast.forecast_confidence` | number | Model confidence (0–100%) | +| `aqi.category` | string | AQI category name (see AQI Categories below) | +| `aqi.label` | string | Health advisory for this AQI level | +| `aqi.trend_message` | string | Expected direction of PM2.5 over the next period | +| `met.air_temperature` | number | Air temperature in °C | +| `met.relative_humidity` | number | Relative humidity in % | +| `met.air_pressure_at_sea_level` | number | Atmospheric pressure in hPa | +| `met.precipitation_amount` | number | Expected rainfall in mm | +| `met.cloud_area_fraction` | number | Cloud cover in % | +| `met.wind_speed` | number | Wind speed in m/s | +| `met.wind_from_direction` | string | Wind direction as compass point (e.g. `"NE"`, `"SW"`) | + +### Hourly forecast item + +Same as daily, with the following differences: + +| Field | Type | Description | +|-------|------|-------------| +| `timestamp` | string | Forecast hour (ISO 8601 datetime with timezone) | +| `forecast.pm2_5_q10` | number | 10th percentile — lower uncertainty bound | +| `forecast.pm2_5_q90` | number | 90th percentile — upper uncertainty bound | + +`pm2_5_low` / `pm2_5_high` are not included in hourly items; use `pm2_5_q10` / `pm2_5_q90` instead. --- -## Error responses +## AQI Categories -### 401 Unauthorised — invalid token +Forecasts include an AQI category derived from the predicted PM2.5 mean value. -```json -{ - "success": false, - "message": "Invalid authentication", - "error": "Unauthorized" -} -``` +| Category | PM2.5 range (µg/m³) | Color | +|----------|---------------------|-------| +| Good | 0.0 – 9.0 | Green (`#00e400`) | +| Moderate | 9.1 – 35.4 | Yellow (`#ffff00`) | +| Unhealthy for Sensitive Groups | 35.5 – 55.4 | Orange (`#ff7e00`) | +| Unhealthy | 55.5 – 125.4 | Red (`#ff0000`) | +| Very Unhealthy | 125.5 – 225.4 | Purple (`#8f3f97`) | +| Hazardous | 225.5+ | Maroon (`#7e0023`) | -### 403 Forbidden — not on Premium Tier +--- -```json -{ - "success": false, - "message": "Forecast access requires Premium Tier subscription", - "error": "Forbidden", - "upgrade_url": "https://analytics.airqo.net/account/subscription" -} -``` +## Validation Error -### 404 Not Found — invalid ID +Returned when more than one of `site_id`, `grid_id`, or `cohort_id` is provided in the same request. ```json { "success": false, - "message": "Device or site not found", - "error": "Not Found" + "message": "Please specify only one of site_id, grid_id, or cohort_id.", + "data": { + "forecasts": [] + } } ``` --- -## Best practices - -- **Cache forecast results** — forecasts update every 6 hours. Caching for 1–2 hours reduces API calls with minimal freshness trade-off. -- **Check `generated_at`** — use this to tell users how recent the forecast is. -- **Always display health tips** — they are the most user-actionable part of the forecast. -- **Prefer near-term data** — the first 48 hours are more accurate than days 4–7. -- **Implement graceful fallback** — if the forecast call fails, display the most recent historical reading instead. - ---- - -## Forecast availability - -| Detail | Value | -|--------|-------| -| Update frequency | Every 6 hours (00:00, 06:00, 12:00, 18:00 UTC) | -| Hourly horizon | 168 hours (7 days) | -| Daily horizon | 7 days | -| Coverage | All active monitoring sites | -| Historical forecasts | Not available | -| Pollutants | PM2.5 (PM10 in future versions) | - ---- - -## Upgrade to Premium +## Caching and Update Frequency -To unlock forecast access, log in to [analytics.airqo.net](https://analytics.airqo.net), go to **Account Settings → Subscription**, and select Premium Tier. +Forecast results are cached per request parameters. Cache is invalidated automatically when the underlying forecast data changes — typically aligned with model run cycles. For most integrations, caching responses for **1–3 hours** gives a good balance between API call volume and data freshness. See [Best Practices →](../reference/best-practices.md) for caching guidance. diff --git a/src/docs-website/docs/api/reference/error-codes.md b/src/docs-website/docs/api/reference/error-codes.md index 00995665fd..7cdbf024cb 100644 --- a/src/docs-website/docs/api/reference/error-codes.md +++ b/src/docs-website/docs/api/reference/error-codes.md @@ -19,7 +19,7 @@ All AirQo API endpoints return consistent error responses so you can handle fail } ``` -For forecast endpoints, the shape uses `success: false`: +For forecast endpoints (`/api/v2/predict/daily-forecasting/`, `/api/v2/predict/hourly-forecasting/`), the shape uses `success: false`: ```json { @@ -57,6 +57,24 @@ For forecast endpoints, the shape uses `success: false`: --- +### 400 Bad Request — conflicting forecast parameters + +```json +{ + "success": false, + "message": "Please specify only one of site_id, grid_id, or cohort_id.", + "data": { + "forecasts": [] + } +} +``` + +**Cause:** More than one of `site_id`, `grid_id`, or `cohort_id` was provided in a single forecast request. + +**Solution:** Provide exactly one identifier per request. + +--- + ### 401 Unauthorized ```json diff --git a/src/docs-website/docs/api/reference/finding-ids.md b/src/docs-website/docs/api/reference/finding-ids.md index 2abccfcb28..b09687666e 100644 --- a/src/docs-website/docs/api/reference/finding-ids.md +++ b/src/docs-website/docs/api/reference/finding-ids.md @@ -14,7 +14,7 @@ To use the AirQo API, you need the right identifier for your data source. This p A Cohort ID groups your organisation's devices together. Cohorts are managed by AirQo. **How to find your Cohort ID:** -- **Public Cohorts:** Browse the metadata endpoints at [docs.airqo.net/airqo-rest-api-documentation/api-endpoints/metadata](https://docs.airqo.net/airqo-rest-api-documentation/api-endpoints/metadata) +- **Public Cohorts:** Use the [Metadata API — List all cohorts](./metadata.md#list-all-public-cohorts) endpoint to browse all publicly visible cohorts - **Private Cohorts:** Contact [support@airqo.net](mailto:support@airqo.net) — your Cohort ID will be shared securely once your organisation's Cohort is set up A Cohort ID looks like: `64b9c7d5e3f82b0014c5a123` @@ -27,8 +27,9 @@ A Grid ID represents a geographical area (typically a city or district). Grids a **How to find your Grid ID:** -1. Visit the [metadata documentation](https://docs.airqo.net/airqo-rest-api-documentation/api-endpoints/metadata) -2. Use the `admin_level` query parameter to filter by country, province, or city +1. Call the [grid summary endpoint](./metadata.md#grid-summary-with-site-details): `GET /api/v2/devices/grids/summary` +2. Use the `admin_level=city` query parameter to filter to city-level grids +3. Locate your city by `long_name` and copy its `_id` value **Known Grid IDs (examples):** @@ -36,7 +37,7 @@ A Grid ID represents a geographical area (typically a city or district). Grids a |------|---------| | Nairobi | `64b7ac8fd7249f0029feca80` | -For other cities, use the metadata endpoint to browse all available Grids. +For other cities, use the [Metadata API](./metadata.md) to browse all available grids. --- @@ -45,8 +46,9 @@ For other cities, use the metadata endpoint to browse all available Grids. A Site ID identifies a specific physical monitoring location. **How to find Site IDs:** -- Call the [recent measurements endpoint](../for-cities/recent-measurements.md) for your Grid and collect the `site_id` field from each measurement -- Browse the metadata endpoints for a full list: [docs.airqo.net/airqo-rest-api-documentation/api-endpoints/metadata](https://docs.airqo.net/airqo-rest-api-documentation/api-endpoints/metadata) +- Call [GET /api/v2/devices/grids/{GRID_ID}/generate](./metadata.md#get-all-site-and-device-ids-for-a-grid) to list every site (and its devices) within a grid +- Call the [Metadata API — List all sites](./metadata.md#list-all-public-sites) to browse all public monitoring sites +- Or call the [recent measurements endpoint](../for-cities/recent-measurements.md) for your Grid and collect the `site_id` field from each measurement record A Site ID looks like: `64f7b3e8c9d25a0013f2d456` @@ -75,6 +77,25 @@ A Device ID uniquely identifies a sensor unit. Device names follow the pattern ` --- +--- + +## Browsing via the Metadata API + +The [Metadata API](./metadata.md) provides dedicated endpoints for discovering every public resource: + +| What you want | Endpoint | +|---------------|----------| +| Browse all grids | `GET /api/v2/devices/metadata/grids` | +| Browse grids by city/country | `GET /api/v2/devices/grids/summary?admin_level=city` | +| Look up one grid | `GET /api/v2/devices/metadata/grids/{GRID_ID}` | +| Sites in a grid | `GET /api/v2/devices/grids/{GRID_ID}/generate` | +| Browse all cohorts | `GET /api/v2/devices/metadata/cohorts` | +| Sites and devices in a cohort | `GET /api/v2/devices/cohorts/{COHORT_ID}/generate` | +| Browse all sites | `GET /api/v2/devices/metadata/sites` | +| Browse all devices | `GET /api/v2/devices/metadata/devices` | + +--- + ## Still can't find your ID? Contact [support@airqo.net](mailto:support@airqo.net) with your organisation's name or the city/region you are monitoring. The team can look up the correct IDs for you. diff --git a/src/docs-website/docs/api/reference/metadata.md b/src/docs-website/docs/api/reference/metadata.md new file mode 100644 index 0000000000..f2f2684e15 --- /dev/null +++ b/src/docs-website/docs/api/reference/metadata.md @@ -0,0 +1,448 @@ +--- +sidebar_position: 6 +sidebar_label: Metadata API +--- + +# Metadata API + +The Metadata API lets you browse the AirQo monitoring infrastructure — grids, cohorts, sites, devices, and geographic boundaries — so you can discover the right identifiers before querying measurement or forecast data. + +All endpoints require your `token` query parameter. See [Authentication →](../getting-started/authentication.md). + +--- + +## Grids + +Grids are geographically-defined groupings of monitoring sites, typically aligned to a city or administrative district. + +### List all grids + +``` +GET /api/v2/devices/metadata/grids?token={SECRET_TOKEN} +``` + +Returns all publicly visible grids. + +**Query parameters** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `token` | string | — | Required | +| `limit` | integer | 1000 | Max results per page (max: 2000) | +| `skip` | integer | 0 | Records to skip | + +**Example response** + +```json +{ + "success": true, + "grids": [ + { + "_id": "64b7ac8fd7249f0029feca80", + "name": "nairobi_city", + "long_name": "Nairobi City", + "admin_level": "city", + "visibility": true, + "network": "airqo", + "createdAt": "2023-07-19T10:25:00.000Z" + } + ], + "meta": { + "total": 42, + "skip": 0, + "limit": 1000, + "page": 1, + "pages": 1 + } +} +``` + +--- + +### Grid summary with site details + +``` +GET /api/v2/devices/grids/summary?token={SECRET_TOKEN} +``` + +Returns grids with their constituent site details nested inside. This is the most useful endpoint for **discovering Grid IDs and the sites within them**. + +**Query parameters** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `token` | string | Required | +| `admin_level` | string | Filter by level: `country`, `province`, `city`, `district`, `subcounty`, `county`, `division`, `parish` | +| `id` | string | Filter by a specific Grid ID | + +**Example — filter to city-level grids** + +```bash +curl "https://api.airqo.net/api/v2/devices/grids/summary?admin_level=city&token=YOUR_SECRET_TOKEN" +``` + +**Example response** + +```json +{ + "success": true, + "grids": [ + { + "_id": "64b7ac8fd7249f0029feca80", + "name": "nairobi_city", + "long_name": "Nairobi City", + "admin_level": "city", + "visibility": true, + "network": "airqo", + "numberOfSites": 12, + "createdAt": "2023-07-19T10:25:00.000Z", + "sites": [ + { + "_id": "64f7b3e8c9d25a0013f2d456", + "name": "nairobi_road", + "long_name": "Nairobi Road, Nairobi", + "latitude": -1.2921, + "longitude": 36.8219, + "data_provider": "airqo" + } + ] + } + ] +} +``` + +--- + +### Get a single grid + +``` +GET /api/v2/devices/metadata/grids/{GRID_ID}?token={SECRET_TOKEN} +``` + +Returns full details for one grid. + +--- + +### Get all site and device IDs for a grid + +``` +GET /api/v2/devices/grids/{GRID_ID}/generate?token={SECRET_TOKEN} +``` + +Returns every site and device ID belonging to the grid. Use this to build a list of `site_id` or `device_id` values for measurement and forecast queries. + +**Example response** + +```json +{ + "success": true, + "sites": [ + { + "site_id": "64f7b3e8c9d25a0013f2d456", + "site_name": "Nairobi Road", + "devices": [ + { + "device_id": "65c8d4a2f1b45c0012a3e789", + "device_name": "airqo_bx2847" + } + ] + } + ] +} +``` + +--- + +### List countries + +``` +GET /api/v2/devices/grids/countries?token={SECRET_TOKEN} +``` + +Returns all countries that have AirQo monitoring coverage. Useful for filtering the grid summary by country before drilling down to a city. + +--- + +## Cohorts + +Cohorts are organisationally-defined groups of devices, managed by the AirQo team on behalf of partner organisations. + +### List all public cohorts + +``` +GET /api/v2/devices/metadata/cohorts?token={SECRET_TOKEN} +``` + +Returns all publicly visible cohorts. + +**Query parameters** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `token` | string | — | Required | +| `limit` | integer | 1000 | Max results per page (max: 2000) | +| `skip` | integer | 0 | Records to skip | + +**Example response** + +```json +{ + "success": true, + "cohorts": [ + { + "_id": "64b9c7d5e3f82b0014c5a123", + "name": "partner_network", + "long_name": "Partner Monitoring Network", + "visibility": true, + "network": "airqo", + "createdAt": "2023-08-01T00:00:00.000Z" + } + ], + "meta": { + "total": 5, + "skip": 0, + "limit": 1000, + "page": 1, + "pages": 1 + } +} +``` + +:::note Private Cohorts +Private cohorts do not appear in this list. If your organisation has a private Cohort, contact [support@airqo.net](mailto:support@airqo.net) to obtain your Cohort ID. +::: + +--- + +### Get a single cohort + +``` +GET /api/v2/devices/metadata/cohorts/{COHORT_ID}?token={SECRET_TOKEN} +``` + +Returns full details for one cohort. + +--- + +### Get all site and device IDs for a cohort + +``` +GET /api/v2/devices/cohorts/{COHORT_ID}/generate?token={SECRET_TOKEN} +``` + +Returns every site and device ID belonging to the cohort. The structure matches the grid generate endpoint. + +**Example response** + +```json +{ + "success": true, + "sites": [ + { + "site_id": "64f7b3e8c9d25a0013f2d456", + "site_name": "Kampala Central", + "devices": [ + { + "device_id": "65c8d4a2f1b45c0012a3e789", + "device_name": "airqo_bx2847" + } + ] + } + ] +} +``` + +--- + +## Sites + +Sites are fixed physical locations where sensors are deployed. + +### List all public sites + +``` +GET /api/v2/devices/metadata/sites?token={SECRET_TOKEN} +``` + +Returns all publicly visible monitoring sites. + +**Query parameters** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `token` | string | — | Required | +| `limit` | integer | 1000 | Max results per page (max: 2000) | +| `skip` | integer | 0 | Records to skip | + +**Example response** + +```json +{ + "success": true, + "sites": [ + { + "_id": "64f7b3e8c9d25a0013f2d456", + "name": "kampala_road", + "long_name": "Kampala Road, Kampala", + "generated_name": "Kampala Road", + "latitude": 0.3476, + "longitude": 32.5825, + "network": "airqo", + "visibility": true, + "online_status": "online" + } + ], + "meta": { + "total": 250, + "skip": 0, + "limit": 1000, + "page": 1, + "pages": 1 + } +} +``` + +--- + +### Get a single site + +``` +GET /api/v2/devices/metadata/sites/{SITE_ID}?token={SECRET_TOKEN} +``` + +Returns full details for one monitoring site, including coordinates, administrative context, and current online status. + +--- + +## Devices + +Devices are individual sensor units. Each device is deployed at a site. + +### List all public devices + +``` +GET /api/v2/devices/metadata/devices?token={SECRET_TOKEN} +``` + +Returns all publicly visible devices. + +**Query parameters** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `token` | string | — | Required | +| `limit` | integer | 1000 | Max results per page (max: 2000) | +| `skip` | integer | 0 | Records to skip | + +**Example response** + +```json +{ + "success": true, + "devices": [ + { + "_id": "65c8d4a2f1b45c0012a3e789", + "name": "airqo_bx2847", + "category": "lowcost", + "status": "deployed", + "site_id": "64f7b3e8c9d25a0013f2d456", + "network": "airqo", + "isOnline": true, + "lastActive": "2025-09-28T08:45:00.000Z" + } + ], + "meta": { + "total": 850, + "skip": 0, + "limit": 1000, + "page": 1, + "pages": 1 + } +} +``` + +**Device categories** + +| Category | Description | +|----------|-------------| +| `lowcost` | Low-cost optical particle sensors (the majority of AirQo devices) | +| `bam` | Beta Attenuation Monitor — reference-grade instruments | +| `gas` | Gas sensor units | + +--- + +### Get a single device + +``` +GET /api/v2/devices/metadata/devices/{DEVICE_ID}?token={SECRET_TOKEN} +``` + +Returns full details for one device, including its deployment site and online status. + +--- + +## Locations + +The locations endpoint lets you browse administrative boundaries at different geographic levels — useful for understanding what areas AirQo covers before querying a grid. + +### List locations + +``` +GET /api/v2/devices/locations?token={SECRET_TOKEN} +``` + +**Query parameters** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `token` | string | Required | +| `admin_level` | string | Filter by level: `country`, `province`, `state`, `district`, `county`, `subcounty`, `division`, `parish`, `village` | +| `name` | string | Filter by location name (partial match) | +| `limit` | integer | Max results per page | +| `skip` | integer | Records to skip | + +**Example — list all countries** + +```bash +curl "https://api.airqo.net/api/v2/devices/locations?admin_level=country&token=YOUR_SECRET_TOKEN" +``` + +--- + +## Pagination + +All list endpoints support `limit` and `skip` parameters for pagination. The `meta` object in every response includes `total`, `page`, and `pages` to help you navigate large result sets. + +**Example — page through all sites in blocks of 100** + +```bash +# Page 1 +curl "https://api.airqo.net/api/v2/devices/metadata/sites?limit=100&skip=0&token=YOUR_SECRET_TOKEN" + +# Page 2 +curl "https://api.airqo.net/api/v2/devices/metadata/sites?limit=100&skip=100&token=YOUR_SECRET_TOKEN" +``` + +--- + +## Common workflows + +### Find a Grid ID for a city + +1. Call the grid summary endpoint with `admin_level=city` +2. Locate your city by `long_name` +3. Copy the `_id` value — that is your Grid ID + +### Find all Site IDs in your Grid + +1. Call `GET /api/v2/devices/grids/{GRID_ID}/generate` +2. Collect the `site_id` from each entry in the `sites` array + +### Find all Device IDs in your Cohort + +1. Call `GET /api/v2/devices/cohorts/{COHORT_ID}/generate` +2. For each site, collect `device_id` values from the nested `devices` array + +--- + +See also: [Finding IDs →](./finding-ids.md) diff --git a/src/docs-website/docs/api/reference/response-structure.md b/src/docs-website/docs/api/reference/response-structure.md index 51178e310f..b4fe09184c 100644 --- a/src/docs-website/docs/api/reference/response-structure.md +++ b/src/docs-website/docs/api/reference/response-structure.md @@ -190,28 +190,36 @@ Used by: `/api/v2/spatial/heatmaps` endpoints. ## Forecast response -Used by: `/api/v2/predict/hourly-forecast` and `/api/v2/predict/daily-forecast`. +Used by: `/api/v2/predict/daily-forecasting/` and `/api/v2/predict/hourly-forecasting/`. + +See [Forecast API →](../forecasts/overview.md) for the full response schema, field reference, and scope-based variants. + +**Daily forecasting — abbreviated example:** ```json { "success": true, - "message": "Forecasts retrieved successfully", - "forecasts": [ - { - "time": "2025-09-29T14:00:00+00:00", - "pm2_5": 24.5, - "health_tips": ["Air quality is acceptable for most people"] - } - ], - "forecast_metadata": { - "model_version": "v2.3", - "generated_at": "2025-09-29T12:00:00+00:00", - "location": { - "site_id": "64f7b3e8c9d25a0013f2d456", - "latitude": 0.3476, - "longitude": 32.5825 - }, - "forecast_horizon_hours": 168 + "data": { + "start_date": "2026-06-03", + "end_date": "2026-06-09", + "days": 7, + "total": 1, + "units": { "pm2_5": "ug/m3", "forecast_confidence": "%" }, + "forecasts": [ + { + "site_details": { "site_id": "site-123", "site_name": "Kampala Central" }, + "forecasts": [ + { + "date": "2026-06-03", + "forecast": { "pm2_5_mean": 28.4, "forecast_confidence": 84.5 }, + "aqi": { "category": "Moderate", "label": "Air quality is acceptable for most people." }, + "met": { "air_temperature": 24.7, "relative_humidity": 72.1 } + } + ] + } + ] } } ``` + +**Hourly forecasting** uses the same envelope but with `start_timestamp`, `end_timestamp`, `hours`, and pagination fields (`page`, `pages`). Individual forecast items use `timestamp` instead of `date` and include `pm2_5_q10` / `pm2_5_q90` uncertainty bounds instead of `pm2_5_low` / `pm2_5_high`. From f3a1045c30f93b0a445590a80cd7a1ed7f182b9d Mon Sep 17 00:00:00 2001 From: baalmart Date: Sun, 7 Jun 2026 23:52:44 +0300 Subject: [PATCH 2/3] fix(docs): address review feedback on API docs accuracy and lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix all issues raised in inline review comments across the forecast and reference documentation pages. - Add `http` language tag to all bare fenced code blocks in forecasts/overview.md (3 blocks) and reference/metadata.md (13 blocks) to satisfy markdownlint MD040 and ensure correct syntax highlighting - Correct `units.wind_from_direction` value from "degrees" to "compass" in both the daily and hourly response examples; the predict microservice converts raw degree values to compass strings (e.g. "NE", "SW") before returning them, making the old unit label internally inconsistent with the example payloads and field reference table - Replace placeholder `site_id: "site-123"` with a realistic 24-char hex ID in forecasts/overview.md (daily + hourly blocks) and reference/response-structure.md to match the documented ID format - Replace placeholder scope IDs ("grid-123") with a realistic 24-char hex ID in the scope metadata example in forecasts/overview.md - Add envelope clarification note before the scope metadata JSON block so readers understand the object is nested inside `{ "success": true, "data": { ... } }`, consistent with all other response examples - Split the "consistent error response" claim in reference/error-codes.md into two explicitly documented formats — Devices/Analytics API (`status`, `message`, `code`) and Forecast API (`success`, `message`, `error`) — each with a field table, replacing the contradictory single "consistent" description - Remove duplicate horizontal rule (`---` appearing twice consecutively) in reference/finding-ids.md --- .../docs/api/forecasts/overview.md | 20 +++++++------- .../docs/api/reference/error-codes.md | 24 ++++++++++++++--- .../docs/api/reference/finding-ids.md | 2 -- .../docs/api/reference/metadata.md | 26 +++++++++---------- .../docs/api/reference/response-structure.md | 2 +- 5 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/docs-website/docs/api/forecasts/overview.md b/src/docs-website/docs/api/forecasts/overview.md index 7a9b55064e..357c8b4f98 100644 --- a/src/docs-website/docs/api/forecasts/overview.md +++ b/src/docs-website/docs/api/forecasts/overview.md @@ -15,7 +15,7 @@ Forecasts require a **Premium Tier** subscription. ## Daily Forecasting Endpoint -``` +```http GET /api/v2/predict/daily-forecasting/ ``` @@ -50,13 +50,13 @@ Only one of `site_id`, `grid_id`, or `cohort_id` may be provided per request. "precipitation_amount": "mm", "cloud_area_fraction": "%", "wind_speed": "m/s", - "wind_from_direction": "degrees", + "wind_from_direction": "compass", "forecast_confidence": "%" }, "forecasts": [ { "site_details": { - "site_id": "site-123", + "site_id": "64f7b3e8c9d25a0013f2d456", "site_name": "Kampala Central", "site_latitude": 0.3476, "site_longitude": 32.5825 @@ -98,7 +98,7 @@ Only one of `site_id`, `grid_id`, or `cohort_id` may be provided per request. ## Hourly Forecasting Endpoint -``` +```http GET /api/v2/predict/hourly-forecasting/ ``` @@ -145,13 +145,13 @@ Only one of `site_id`, `grid_id`, or `cohort_id` may be provided per request. "precipitation_amount": "mm", "cloud_area_fraction": "%", "wind_speed": "m/s", - "wind_from_direction": "degrees", + "wind_from_direction": "compass", "forecast_confidence": "%" }, "forecasts": [ { "site_details": { - "site_id": "site-123", + "site_id": "64f7b3e8c9d25a0013f2d456", "site_name": "Kampala Central", "site_latitude": 0.3476, "site_longitude": 32.5825 @@ -193,7 +193,7 @@ Only one of `site_id`, `grid_id`, or `cohort_id` may be provided per request. When grid-based or cohort-based forecasting is requested, the API supports aggregated forecast retrieval through: -``` +```http GET /api/v2/predict/hourly-forecasting/ GET /api/v2/predict/daily-forecasting/ ``` @@ -202,12 +202,14 @@ The `` path parameter accepts a grid ID or cohort ID. Use the optional ### Scope Metadata Returned +The fields below are returned inside the top-level `data` object (`{ "success": true, "data": { ... } }`). + ```json { "scope": { "type": "grid", - "id": "grid-123", - "grid_id": "grid-123" + "id": "64b7ac8fd7249f0029feca80", + "grid_id": "64b7ac8fd7249f0029feca80" }, "sites_count": 3, "sites_with_forecasts_count": 2, diff --git a/src/docs-website/docs/api/reference/error-codes.md b/src/docs-website/docs/api/reference/error-codes.md index 7cdbf024cb..5ba11bd49b 100644 --- a/src/docs-website/docs/api/reference/error-codes.md +++ b/src/docs-website/docs/api/reference/error-codes.md @@ -5,11 +5,15 @@ sidebar_label: Error Codes # Error Codes -All AirQo API endpoints return consistent error responses so you can handle failures programmatically. +AirQo API endpoints use two different error response formats depending on the API family. Match your error handling to the endpoint you are calling. --- -## Error response format +## Error response formats + +### Devices and Analytics API + +Used by: measurement endpoints (`/api/v2/devices/...`), Analytics API (`/api/v3/public/analytics/...`), and metadata endpoints (`/api/v2/devices/metadata/...`). ```json { @@ -19,7 +23,15 @@ All AirQo API endpoints return consistent error responses so you can handle fail } ``` -For forecast endpoints (`/api/v2/predict/daily-forecasting/`, `/api/v2/predict/hourly-forecasting/`), the shape uses `success: false`: +| Field | Type | Description | +|-------|------|-------------| +| `status` | string | `"error"` on failure | +| `message` | string | Human-readable description | +| `code` | string | Machine-readable error code (e.g. `"NOT_FOUND"`, `"UNAUTHORIZED"`) | + +### Forecast API + +Used by: `/api/v2/predict/daily-forecasting/` and `/api/v2/predict/hourly-forecasting/`. ```json { @@ -29,6 +41,12 @@ For forecast endpoints (`/api/v2/predict/daily-forecasting/`, `/api/v2/predict/h } ``` +| Field | Type | Description | +|-------|------|-------------| +| `success` | boolean | Always `false` on error | +| `message` | string | Human-readable description | +| `error` | string | HTTP status phrase (e.g. `"Unauthorized"`, `"Forbidden"`) | + --- ## HTTP status codes diff --git a/src/docs-website/docs/api/reference/finding-ids.md b/src/docs-website/docs/api/reference/finding-ids.md index b09687666e..853c85b9e8 100644 --- a/src/docs-website/docs/api/reference/finding-ids.md +++ b/src/docs-website/docs/api/reference/finding-ids.md @@ -77,8 +77,6 @@ A Device ID uniquely identifies a sensor unit. Device names follow the pattern ` --- ---- - ## Browsing via the Metadata API The [Metadata API](./metadata.md) provides dedicated endpoints for discovering every public resource: diff --git a/src/docs-website/docs/api/reference/metadata.md b/src/docs-website/docs/api/reference/metadata.md index f2f2684e15..c1da52bb77 100644 --- a/src/docs-website/docs/api/reference/metadata.md +++ b/src/docs-website/docs/api/reference/metadata.md @@ -17,7 +17,7 @@ Grids are geographically-defined groupings of monitoring sites, typically aligne ### List all grids -``` +```http GET /api/v2/devices/metadata/grids?token={SECRET_TOKEN} ``` @@ -61,7 +61,7 @@ Returns all publicly visible grids. ### Grid summary with site details -``` +```http GET /api/v2/devices/grids/summary?token={SECRET_TOKEN} ``` @@ -115,7 +115,7 @@ curl "https://api.airqo.net/api/v2/devices/grids/summary?admin_level=city&token= ### Get a single grid -``` +```http GET /api/v2/devices/metadata/grids/{GRID_ID}?token={SECRET_TOKEN} ``` @@ -125,7 +125,7 @@ Returns full details for one grid. ### Get all site and device IDs for a grid -``` +```http GET /api/v2/devices/grids/{GRID_ID}/generate?token={SECRET_TOKEN} ``` @@ -155,7 +155,7 @@ Returns every site and device ID belonging to the grid. Use this to build a list ### List countries -``` +```http GET /api/v2/devices/grids/countries?token={SECRET_TOKEN} ``` @@ -169,7 +169,7 @@ Cohorts are organisationally-defined groups of devices, managed by the AirQo tea ### List all public cohorts -``` +```http GET /api/v2/devices/metadata/cohorts?token={SECRET_TOKEN} ``` @@ -216,7 +216,7 @@ Private cohorts do not appear in this list. If your organisation has a private C ### Get a single cohort -``` +```http GET /api/v2/devices/metadata/cohorts/{COHORT_ID}?token={SECRET_TOKEN} ``` @@ -226,7 +226,7 @@ Returns full details for one cohort. ### Get all site and device IDs for a cohort -``` +```http GET /api/v2/devices/cohorts/{COHORT_ID}/generate?token={SECRET_TOKEN} ``` @@ -260,7 +260,7 @@ Sites are fixed physical locations where sensors are deployed. ### List all public sites -``` +```http GET /api/v2/devices/metadata/sites?token={SECRET_TOKEN} ``` @@ -306,7 +306,7 @@ Returns all publicly visible monitoring sites. ### Get a single site -``` +```http GET /api/v2/devices/metadata/sites/{SITE_ID}?token={SECRET_TOKEN} ``` @@ -320,7 +320,7 @@ Devices are individual sensor units. Each device is deployed at a site. ### List all public devices -``` +```http GET /api/v2/devices/metadata/devices?token={SECRET_TOKEN} ``` @@ -373,7 +373,7 @@ Returns all publicly visible devices. ### Get a single device -``` +```http GET /api/v2/devices/metadata/devices/{DEVICE_ID}?token={SECRET_TOKEN} ``` @@ -387,7 +387,7 @@ The locations endpoint lets you browse administrative boundaries at different ge ### List locations -``` +```http GET /api/v2/devices/locations?token={SECRET_TOKEN} ``` diff --git a/src/docs-website/docs/api/reference/response-structure.md b/src/docs-website/docs/api/reference/response-structure.md index b4fe09184c..2b45ddda8a 100644 --- a/src/docs-website/docs/api/reference/response-structure.md +++ b/src/docs-website/docs/api/reference/response-structure.md @@ -207,7 +207,7 @@ See [Forecast API →](../forecasts/overview.md) for the full response schema, f "units": { "pm2_5": "ug/m3", "forecast_confidence": "%" }, "forecasts": [ { - "site_details": { "site_id": "site-123", "site_name": "Kampala Central" }, + "site_details": { "site_id": "64f7b3e8c9d25a0013f2d456", "site_name": "Kampala Central" }, "forecasts": [ { "date": "2026-06-03", From 974ae05bef70ec4566ff79f9493065cb43ced4d8 Mon Sep 17 00:00:00 2001 From: baalmart Date: Mon, 8 Jun 2026 00:19:48 +0300 Subject: [PATCH 3/3] fix(docs): escape {GRID_ID} in finding-ids link text to prevent MDX ReferenceError The link display text contained a bare {GRID_ID} placeholder which MDX evaluated as a JSX expression, throwing ReferenceError at static site generation. Moved the path template into an adjacent code span instead. --- src/docs-website/docs/api/reference/finding-ids.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs-website/docs/api/reference/finding-ids.md b/src/docs-website/docs/api/reference/finding-ids.md index 853c85b9e8..3d16f5417e 100644 --- a/src/docs-website/docs/api/reference/finding-ids.md +++ b/src/docs-website/docs/api/reference/finding-ids.md @@ -46,7 +46,7 @@ For other cities, use the [Metadata API](./metadata.md) to browse all available A Site ID identifies a specific physical monitoring location. **How to find Site IDs:** -- Call [GET /api/v2/devices/grids/{GRID_ID}/generate](./metadata.md#get-all-site-and-device-ids-for-a-grid) to list every site (and its devices) within a grid +- Call the [grid generate endpoint](./metadata.md#get-all-site-and-device-ids-for-a-grid) (`GET /api/v2/devices/grids/{GRID_ID}/generate`) to list every site (and its devices) within a grid - Call the [Metadata API — List all sites](./metadata.md#list-all-public-sites) to browse all public monitoring sites - Or call the [recent measurements endpoint](../for-cities/recent-measurements.md) for your Grid and collect the `site_id` field from each measurement record