Add your sensor
One device today. More as each is proven.
openaqi opens with UniFi Protect air quality sensors — the hardware we run ourselves, so the one we can actually vouch for. Ten minutes, one open-source binary, and your coordinates are never published.
The narrow start is deliberate. A reading carries no proof of what produced it, and inside a ~5 km cell a single sensor often sets the number an area is judged by. Opening one device at a time is what keeps that number worth trusting. If you run something else, tell us what it is — that is how the next one gets added.
Before anything else: your location
You give coordinates once, when you register a station. They are used to work out which ~5 km cell you are in, stored in one table nothing public reads, and never returned by any endpoint. Readings themselves carry no position at all — the table they live in has no coordinate column, which is a property of the schema rather than a promise about our behaviour.
You do not have to take our word for it. Run the agent with --dry-run and it prints exactly what it would send, and sends nothing.
Register a station
Create an account and add a station with roughly where the sensor is. Anywhere within a kilometre or two gives the same cell, so the exact number does not matter — and being approximate is fine by us.
You get a station key immediately. It writes readings for that one station and can do nothing else: it cannot read the API, and it cannot write for anyone else's sensor.
Register a stationChoose how to send readings
Three routes, in increasing order of effort. Most people want the first two.
A. Run the agent with a built-in source
Your hardware is one we have already written a driver for.
One static binary. No runtime to install, no dependencies, and it survives your network going down — readings are buffered on disk and replayed when the connection returns, with the timestamp they were taken, not the one they were finally delivered.
install
curl -sSL https://openaqi.net/install.sh | shOne built-in source today: UniFi Protect, walked through below.
Send the unit if it is not ours
Devices genuinely disagree. NO₂ arrives as ppb from one sensor and µg/m³ from another; temperature as °C or °F. Add unit and the server converts it:
{"metric": "temperature", "value": 71.6, "unit": "F"} → stored as 22.0 °C
{"metric": "no2", "value": 40, "unit": "ug/m3"} → stored as 21.3 ppbThis matters more than it looks. A Fahrenheit reading sent without its unit is stored as Celsius and there is no way to tell afterwards — 71.6 is a plausible number in both. The agent warns when a value only makes sense in another unit, but it cannot catch the cases where both readings are plausible, which is why the field exists.
Metric names are forgiving: pm25, PM2.5 and pm2_5 all land in the same column.
UniFi Protect
The one device-specific integration we ship today, because it is the hardware we run ourselves and therefore the hardware we can test against. More will follow one at a time — a driver written against a datasheet and never run is worse than no driver, because it fails quietly and the numbers still look plausible.
If your device is not on that list, nothing is missing: route B above is the same amount of work, and its readings are indistinguishable once stored.
Two ways in, depending on whether you want the readings for yourself as well.
If you want your own history too: the dashboard
unifi-protect-airquality-dashboard is a self-hosted dashboard for the UniFi air quality sensor. It reads the console directly, keeps the full history in your own ClickHouse, and draws the charts the Protect app does not. It is open source and it is not ours to gate — openaqi is one optional output of it.
If you already run it, forwarding is one variable:
that is the entire integration
OPENAQI_KEY=oaq_your_station_keyNo key, no forwarding. With one, it sends a batch a minute and nothing changes about how it behaves locally: your ClickHouse is written first and openaqi is fed from a separate buffer, so us being slow, down or misconfigured cannot delay or lose a local reading. A network failure keeps the readings and retries; a refused key stops immediately with a message rather than hammering us forever.
check before you trust — logs the exact payload, sends nothing
OPENAQI_DRY_RUN=trueCounters and the last error show up in its own /api/health under openaqi, so you can tell forwarding is working without leaving your own dashboard.
If you only want to contribute: the agent
One binary, no database, no dashboard. It reads the same console and sends readings on, keeping nothing.
You need a local console account. Not a Ubiquiti cloud login, and not an API key. UniFi's documented Integration API does not expose air quality at all — its sensor endpoints return an error for those fields, and the readings only exist behind a session cookie. Create a local user on the console with read access to Protect.
1. see what it would send, without sending anything
openaqi-agent --source unifi \
--unifi-host 10.0.0.1 \
--unifi-user your-local-user \
--unifi-pass '••••••••' \
--dry-runThat prints the exact payload. If you have more than one sensor, add --unifi-sensor "Bedroom" — and if the name is wrong the agent lists the ones it found rather than failing silently.
2. run it for real
openaqi-agent --source unifi \
--unifi-host 10.0.0.1 \
--unifi-user your-local-user \
--unifi-pass '••••••••' \
--key oaq_your_station_keyYour readings appear on the map within a minute or two. Keeping it running under systemd, the full flag list, which metrics this sensor reports and how to tell forwarding is actually working are all in the reference — kept in one place rather than two, so they cannot drift apart.
Full UniFi Protect reference →What we can store
34 metrics. Send whichever ones your device has — nobody reports all of them, and a sensor that only knows PM2.5 is a perfectly good contributor.
index
AQI · VOC · NOx index · TVOC
particulate
PM1.0 · PM2.5 · PM4.0 · PM10 · PC0.3 · PC0.5 · PC1.0 · PC2.5 · PC5.0 · PC10
gas
CO₂ · eCO₂ · CO · NO₂ · NO · NOx · Ozone · SO₂ · NH₃ · H₂S · Formaldehyde · Benzene · TVOC (conc.) · Radon
environment
Temperature · Humidity · Pressure · Noise · Light · UV index
The full catalogue with units, ranges and thresholds is at /docs and machine-readable at /api/v1/meta.
What we ask in return
Site it honestly
Outdoors if you are reporting outdoor air; away from a stove, a printer or a wall vent if you are not. A sensor next to a kettle is not wrong, it is just measuring the kettle — and it becomes part of an average other people rely on.
Do not send synthetic data
Simulated, smoothed or back-filled readings poison a dataset in a way that is impossible to detect later. If you are testing, say so and we will mark the station.
Tell us when it breaks
A sensor that has drifted or died is worse than no sensor, because it still looks like data. Your account shows when each station last reported and how many readings were rejected.