Skip to main content

Historical Data — Partners

Access the full history of calibrated air quality measurements for the devices in your Cohort. Historical data is retrieved through the Analytics API using a POST request. Each individual request is capped at approximately 2 months of data — use batched requests with pagination to retrieve longer periods.

Tier requirement

Historical data access requires a Standard Tier subscription or above.

Cohort ID direct filtering — coming soon

The Analytics API does not yet accept cohort_id as a request parameter. You must supply individual device names (device_names) in the request body instead.

Workaround: Call the Metadata API (GET /api/v2/devices/cohorts/{COHORT_ID}/generate) to retrieve the device_name values for your Cohort, then pass them as the device_names parameter in your Analytics API request.

Direct Cohort ID filtering will be added to the Analytics API in a future release.


Overview

Historical data for your Cohort is fetched via the Analytics API by specifying the device names that belong to your Cohort. Use the Metadata API (GET /api/v2/devices/cohorts/{COHORT_ID}/generate) to retrieve all device names for your Cohort.


Endpoint

POST https://api.airqo.net/api/v3/public/analytics/data-download?token=YOUR_SECRET_TOKEN
Content-Type: application/json

Request parameters

ParameterTypeRequiredDescription
networkstringYesAlways "airqo"
startDateTimestringYesStart of range (ISO 8601)
endDateTimestringYesEnd of range (ISO 8601)
datatypestringYes"calibrated" for quality-controlled data
downloadTypestringYes"json"
frequencystringYes"hourly" or "daily"
device_namesarrayNoFilter to specific devices in your Cohort
pollutantsarrayNoe.g. ["pm2_5", "pm10"]
metaDataFieldsarrayNoe.g. ["latitude", "longitude"]
weatherFieldsarrayNoe.g. ["temperature", "humidity"]
cursorstringNoPagination cursor from previous response

Example request — hourly calibrated data

curl -X POST "https://api.airqo.net/api/v3/public/analytics/data-download?token=YOUR_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"network": "airqo",
"datatype": "calibrated",
"downloadType": "json",
"startDateTime": "2025-01-01T00:00:00Z",
"endDateTime": "2025-01-31T23:59:59Z",
"device_names": ["airqo_bx2847", "airqo_cy1523"],
"pollutants": ["pm2_5", "pm10"],
"metaDataFields": ["latitude", "longitude"],
"frequency": "hourly"
}'

Python:

import requests

token = 'YOUR_SECRET_TOKEN'

payload = {
"network": "airqo",
"datatype": "calibrated",
"downloadType": "json",
"startDateTime": "2025-01-01T00:00:00Z",
"endDateTime": "2025-01-31T23:59:59Z",
"device_names": ["airqo_bx2847", "airqo_cy1523"],
"pollutants": ["pm2_5", "pm10"],
"metaDataFields": ["latitude", "longitude"],
"frequency": "hourly"
}

response = requests.post(
f"https://api.airqo.net/api/v3/public/analytics/data-download?token={token}",
json=payload
)

data = response.json()
print(f"Retrieved {len(data['data'])} records. More available: {data['metadata']['has_more']}")

JavaScript:

const response = await fetch(
`https://api.airqo.net/api/v3/public/analytics/data-download?token=${token}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
network: 'airqo',
datatype: 'calibrated',
downloadType: 'json',
startDateTime: '2025-01-01T00:00:00Z',
endDateTime: '2025-01-31T23:59:59Z',
device_names: ['airqo_bx2847', 'airqo_cy1523'],
pollutants: ['pm2_5', 'pm10'],
metaDataFields: ['latitude', 'longitude'],
frequency: 'hourly'
})
}
);
const data = await response.json();

Example response

{
"status": "success",
"message": "Data downloaded successfully",
"data": [
{
"site_name": "Kampala Road",
"device_name": "airqo_bx2847",
"datetime": "2025-01-01 10:00:00Z",
"pm2_5": 12.45,
"pm10": 15.32,
"latitude": 0.33,
"longitude": 32.56,
"temperature": 24.5,
"humidity": 65.4,
"network": "airqo",
"frequency": "hourly"
}
],
"metadata": {
"total_count": 500,
"has_more": false,
"next": null
}
}

Paginating through large datasets

When metadata.has_more is true, pass the metadata.next cursor in your next request:

import requests

token = 'YOUR_SECRET_TOKEN'
base_payload = {
"network": "airqo",
"datatype": "calibrated",
"downloadType": "json",
"startDateTime": "2025-01-01T00:00:00Z",
"endDateTime": "2025-02-28T23:59:59Z",
"device_names": ["airqo_bx2847"],
"pollutants": ["pm2_5", "pm10"],
"frequency": "hourly"
}

all_records = []
cursor = None

while True:
payload = {**base_payload}
if cursor:
payload['cursor'] = cursor

response = requests.post(
f"https://api.airqo.net/api/v3/public/analytics/data-download?token={token}",
json=payload
).json()

all_records.extend(response['data'])

if not response['metadata']['has_more']:
break

cursor = response['metadata']['next']

print(f"Total records fetched: {len(all_records)}")

Choosing frequency

FrequencyWhen to use
"hourly"Time-series charts, detailed trend analysis
"daily"Monthly reports, long-term comparisons

Tips

  • Break requests into ~2-month batches — each request is capped at approximately 2 months of data. For best performance on large device networks, use 30-day windows per batch.
  • Request only the pollutants you need — omitting unused fields reduces response size.
  • Cache results on your side to avoid repeating identical queries.