Calibrated Data Download
Retrieve AI-calibrated PM2.5 and PM10 measurements at hourly or daily resolution. Calibrated data has been processed through AirQo's machine learning models to correct for environmental factors such as temperature and humidity, making it more suitable for analysis and reporting than raw readings.
Requires a Standard Tier subscription or above.
This endpoint does not yet accept cohort_id as a parameter. Filter by sites, device_names, or device_ids instead. Use the Metadata API to discover identifiers for your Cohort.
Grid ID filtering is supported via grid_ids, currently limited to a single Grid ID per request.
Endpoint
POST https://api.airqo.net/api/v3/public/analytics/data-download?token=YOUR_SECRET_TOKEN
Content-Type: application/json
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
network | string | No | Always "airqo" (default) |
startDateTime | string | Yes | Start timestamp (ISO 8601) |
endDateTime | string | Yes | End timestamp (ISO 8601) |
datatype | string | No | "calibrated" (default) for quality-controlled data |
downloadType | string | No | "json" (default) or "csv" |
frequency | string | No | "hourly" or "daily" (default) |
device_category | string | No | Filter by device type (e.g. "lowcost") |
device_names | array | No* | Specific device identifiers |
device_ids | array | No* | Specific device IDs (as returned by the Metadata API) |
sites | array | No* | Specific Site IDs |
grid_ids | array | No* | A Grid ID — currently limited to one per request |
pollutants | array | No | e.g. ["pm2_5", "pm10"] |
metaDataFields | array | No | e.g. ["latitude", "longitude"] |
weatherFields | array | No | e.g. ["temperature", "humidity"] |
cursor | string | No | Pagination cursor from previous response |
* Provide exactly one of sites, device_names, device_ids, or grid_ids.
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-02T00:00:00Z",
"device_category": "lowcost",
"sites": ["64f7b3e8c9d25a0013f2d456"],
"pollutants": ["pm2_5", "pm10"],
"metaDataFields": ["latitude", "longitude"],
"weatherFields": ["temperature", "humidity"],
"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",
"sites": ["64f7b3e8c9d25a0013f2d456"],
"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()
for record in data['data']:
print(f"{record['datetime']} — {record['device_name']}: PM2.5 = {record['pm2_5']} μg/m³")
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',
sites: ['64f7b3e8c9d25a0013f2d456'],
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
}
}
The data array above is abbreviated to one record for readability — a real page returns up to total_count records.
Response fields
| Field | Description |
|---|---|
site_name | Human-readable site name |
device_name | Device identifier |
datetime | Measurement timestamp |
pm2_5 | Calibrated PM2.5 in μg/m³ |
pm10 | Calibrated PM10 in μg/m³ |
latitude / longitude | Device location |
temperature | Ambient temperature (°C) |
humidity | Relative humidity (%) |
frequency | "hourly" or "daily" |
metadata.has_more | Whether more pages are available |
metadata.next | Cursor for the next page |
Calibrated vs raw data
| Aspect | Calibrated | Raw |
|---|---|---|
| Endpoint | data-download | raw-data |
| Processing | ML-corrected for temperature & humidity | Unprocessed sensor output |
| Resolution | Hourly or daily | Minute-level |
| Use case | Analysis, dashboards, reporting | Advanced research, custom calibration |
| Data quality | Higher (validated) | Lower (may have noise) |
Pagination
Use cursor-based pagination for large date ranges. See Pagination → for a complete walkthrough.
This endpoint accepts at most 10 requests per minute per client.