IRCELINE, Belgian air quality.
Belgium's official air-quality measurements are open, unauthenticated and served by a 52°North Sensor Observation Service. The data is excellent. The API is a scientific instrument rather than a product API, and its proximity search does not filter by proximity — which is worth knowing before you ship a “nearest station” feature.
What IRCELINE is
IRCEL — CELINE (Intergewestelijke Cel voor het Leefmilieu / Cellule Interrégionale de l'Environnement) is the joint body of Belgium's three regional environment agencies: VMM for Flanders, ISSeP and AWAC for Wallonia, and Leefmilieu Brussel for the capital region. The regions own and operate the monitoring stations; IRCELINE aggregates and publishes them. So “the VMM station” and “the IRCELINE station” are frequently the same physical box, reached through different front doors.
The API at geo.irceline.be/sos/api/v1 is a 52°North SOS instance. No key, no registration, no Authorization header. It speaks in timeseries: one measured quantity, from one instrument, at one station.
The data model in one paragraph
A station has a location and a name. A phenomenon is what is being measured — NO₂, PM10, ozone. A timeseries is the pairing of the two, plus the specific instrument doing the measuring. You find timeseries, then you ask a timeseries for values. A station measuring four pollutants has four timeseries, with four different ids, none of which is the station id.
Finding phenomenon ids
curl -s "https://geo.irceline.be/sos/api/v1/phenomena"
# 23 phenomena. The four that matter for street-level air quality:
# 8 Nitrogen dioxide
# 38 Nitrogen monoxide
# 5 Particulate Matter < 10 µm
# 6001 Particulate Matter < 2.5 µmThese ids are stable and are what you pass as ?phenomenon=. Note that PM2.5 is 6001, not something adjacent to PM10's 5 — there is no arithmetic relationship between the ids, so look them up rather than guessing.
Finding timeseries near a point
curl -s "https://geo.irceline.be/sos/api/v1/timeseries\
?near=4.4025,51.2194,10000\
&phenomenon=8\
&limit=2"[
{
"id": "6281",
"label": "Nitrogen dioxide 6281 - TEI 42C - procedure, 40LD02 - Laakdal",
"uom": "µg/m³",
"station": {
"type": "Feature",
"properties": { "id": 1059, "label": "40LD02 - Laakdal" },
"geometry": { "type": "Point",
"coordinates": [5.021545977609842, 51.120384137099414, "NaN"] }
}
},
{
"id": "6504",
"label": "Nitrogen dioxide 6504 - vanaf 970317 AC-31M (Environneme - procedure, 41B001 - Arts-Loi",
"uom": "µg/m³",
"station": {
"type": "Feature",
"properties": { "id": 1109, "label": "41B001 - Arts-Loi" },
"geometry": { "type": "Point",
"coordinates": [4.3686812234614925, 50.84571510092836, "NaN"] }
}
}
]near- Three comma-separated values, longitude first:
lng,lat,radiusMeters. Read the warning below before you trust the third one. phenomenon- Phenomenon id from
/phenomena. limit- Caps the number of timeseries returned. This cap is applied before you get a chance to sort, which matters.
id- The timeseries id —
"6281"— and what you pass togetData. station.properties.id- The station id —
1059. A number here, while the timeseries id is a string. Not interchangeable. uom- Unit of measure, e.g.
µg/m³. Read it rather than assuming; it is right there.
Three things that will catch you
1. The radius in near is not applied
This is the big one. Two requests for the same point, one asking for 5 km and one for 10 km, return the identical set of timeseries in the identical order — and that set includes stations 40 km and 65 km away. Measured on 2026-08-19 from 4.4025,51.2194 (Antwerp Groenplaats):
# Same point, two radii. Distances computed from the returned coordinates.
near=4.4025,51.2194,5000 → 40.7 km, 3.9 km, 34.6 km, 10.5 km, 65.4 km
near=4.4025,51.2194,10000 → 40.7 km, 3.9 km, 34.6 km, 10.5 km, 65.4 km
# ^ identical set, identical orderTwo consequences. The results are not sorted by distance, so timeseries[0] is not the nearest station — in the run above it was a station in Brussels. And the radius does not bound the set, so a limit can cut off the station that is actually next door before you ever see it. Compute haversine distance yourself from station.geometry.coordinates, sort, then filter, and set limit generously enough that the nearest station survives the trip.
2. The third coordinate is the string "NaN"
Station geometry is [lng, lat, "NaN"] — a two-element coordinate with a string in the altitude slot, not a number and not null. Typing the tuple as [number, number, number]compiles and then surprises you. Type it as [number, number, number | "NaN"] and destructure the two you want.
3. Timestamps are Unix milliseconds, and the timespan syntax is ISO 8601
Values come back as { timestamp, value } with timestamp in milliseconds since the epoch, while the timespan you send is an ISO 8601 interval — PT2H/2026-08-19TZmeaning “the two hours ending at that instant”. Two different time formats in one round trip.
Fetching values
curl -s "https://geo.irceline.be/sos/api/v1/timeseries/6281/getData?timespan=PT2H/2026-08-19TZ"{"values":[
{"timestamp":1787090400000,"value":11.5},
{"timestamp":1787094000000,"value":15.0},
{"timestamp":1787097600000,"value":12.5}
]}Values are hourly. An empty valuesarray is normal and means the instrument reported nothing in that window — a station under maintenance, or a gap in validation. Treat it as “unknown”, never as zero: a NO₂ reading of 0 µg/m³ next to a ring road is a bug, not clean air.
Asking for a wider window than you need is the cheap way to get a trend for free. Stadiq pulls two hours and compares first to last to get a rising or falling percentage, which costs the same request as pulling the latest value alone.
Making a number mean something
A raw µg/m³ figure means nothing to a non-specialist, so compare it to a published limit. The two that matter for NO₂ are the WHO 2021 annual guideline of 10 µg/m³ and the EU annual limit value of 40 µg/m³. They are four times apart, so which one you cite decides whether your reading looks fine or alarming — show both, and say which is which.
Be careful about the comparison itself: an annual guideline is not a threshold for a single hourly reading. “This hour is at 150% of the WHO annual guideline” is an honest sentence. “This hour exceeds WHO limits” is not quite one.
Measurements versus forecasts
IRCELINE gives you what was measured. For what is coming, Stadiq uses Open-Meteo's air-quality API, which is also keyless and returns an hourly grid rather than station points:
curl -s "https://air-quality-api.open-meteo.com/v1/air-quality\
?latitude=51.2194&longitude=4.4025\
&hourly=nitrogen_dioxide,pm10,pm2_5\
&forecast_days=1&timezone=Europe%2FBrussels"Keep the two apart in whatever you build. A modelled grid value and a measured station value are not the same kind of fact, and blending them into one number means neither can be checked. Stadiq's GET /api/v1/air-quality returns measured values only for exactly this reason.
Open-Meteo's free tier is for non-commercial use, capped below 10,000 calls a day, and the data is CC-BY 4.0 — attribution required. Commercial use needs their paid tier. Check their terms before you build a business on it.
Licence and attribution
IRCELINE publishes open data and maintains an open-data repository on GitHub. We did not find a published rate limit or a formal terms-of-use page for the SOS API, so treat it as a shared public service: cache aggressively, do not hammer it, and credit IRCEL — CELINE and the regional agency behind the station wherever you display a reading.
How Stadiq uses it
Stadiq pulls NO₂ timeseries near an address, picks the genuinely nearest station by computed distance, reads the last two hours for a value and a trend, and compares it to both the WHO and EU figures. That is what GET /api/v1/air-quality returns, along with whether the point sits inside the Antwerp low-emission zone. See the API reference.
Read next
- GIPODEvery permitted road work and public-domain occupation in Flanders, over an open OGC API Features service that needs no key.
- De Lijn realtimeDetours and stop data for Flemish public transport, and which of De Lijn's API products actually carries what.
- Antwerp open dataWhat the city of Antwerp publishes, where the ArcGIS endpoints are, and how to query them as GeoJSON.
- Stadiq CLIInstall the `stadiq` command, store a key, and read disruptions or a briefing from a terminal or a cron job.
- Stadiq MCP serverThe same four endpoints as read-only agent tools, for Claude Desktop, Cursor or any MCP client.