This document is intended mainly for application developers that want to use air quality data in their projects. We recommend reading this documentation, especially sections explaining some basic concepts. We hope this documentation will be helpful in the process of integrating with our API. We make every effort to keep this documentation up to date and complete. If you have any questions please don't hesitate do contact us. This documentation is open-source and can be edited on GitHub.
This documentation applies to API version 2.0. Comparing to previous version we are happy to announce lots of improvements:
Since 28.02.2019 the support for API 1.0 has been dropped.
Airly API is based on REST. Our API has predictable and intuitive URLs, organized around resources (Installations and Measurements). We use built-in HTTP features for issuing requests (HTTP methods) and handling errors (HTTP statuses).
It is possible to interact with our API from a client-side web applications served from a different domain - our API supports cross-origin requests.
All the API endpoints return content in JSON format (including errors) along with Content-Type: application/json
HTTP header encoded using charset UTF-8
, unless noted otherwise in the endpoint description.
Using our API requires registering an account. It is also possible to log in with existing GitHub, Google or Facebook account.
A registered user is provided with an apikey
which should be passed on each API request for authentication. The API key can be passed in a request as a custom header named apikey or as a query parameter.
To authenticate, Airly API expects you to pass an apikey
header with your API key passed in as a value.
apikey: <apikey>
curl -X GET \
--header 'Accept: application/json' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/meta/indexes'
On a free plan, API consumer is required to use our API only in non-commercial projects.
When presenting data obtained from our API you should present our logo: svg, png.
More details are available in our Terms of Service.
In order to maintain high API throughput, availability and quality of the service the API access is rate-limited.
Default rate limit per apikey
is 100 API requests per day for all users.
All the HTTP requests are counted towards the limit, regardless of whether the request succeeded or not. Each request decrements the currently available limit by 1. The counters are reset each day at midnight UTC (the daily limit) and every full minute (the minute limit).
In case of applications displaying data continuously we recommend caching API responses in local storage and refreshing them periodically.
In case of exceeding the limit API request will return HTTP 429 - Too Many Requests
.
If you need an increased limit or want to use our data in a commercial project please contact us.
Each API response contains additional HTTP headers that inform about current API key limits and their usage. Header names begin with X-RateLimit-Limit-
and X-RateLimit-Remaining-
.
X-RateLimit-Limit-day: 100 X-RateLimit-Remaining-day: 96
Some API endpoints return textual content, e.g. description of current air quality. Such texts are translated and returned in a language according to user preference.
To select API content language you should include an Accept-Language
HTTP header in your request with value set to desired language.
Currently supported languages are English ("en" - default) and Polish ("pl").
Correctly issued API requests result in 200 OK
HTTP status and a JSON content. Otherwise an HTTP error code status is returned, depending on situation. Table below explains it in detail:
400 - Bad Request | request was incorrect, e.g. query parameters were invalid |
401 - Unauthorized | no API key was provided |
404 - Not Found | request path or parameters point to a non-existent resource |
405 - Method Not Allowed | request attempts to use invalid HTTP method (e.g. POST instead of GET) |
406 - Not Acceptable | request attempts to use unsupported content type (e.g. Accept text/xml instead of application/json) |
429 - Too Many Requests | API rate limit was exceeded (see Limits) |
500 - Internal Server Error | a server error has occured |
Additionally in case of an error a JSON response is returned in following format:
{
"errorCode": <error code>,
"message": <message describing error>,
"details": {
<key-value map with more details describing the error>
}
}
curl -X GET \
--header 'Accept: application/json' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/measurements/point?lat=200&lng=19.940984'
HTTP Status: 400
{
"errorCode": "API_REQUEST_INVALID",
"message": "API Request was not valid",
"details": {
"violations": [
{
"parameter": "lat",
"message": "latitude value must be between -90.0 and +90.0",
"rejectedValue": 200
}
]
}
}
All coordinates that occur in the API (both as query parameters and as returned payload fields) are according to WGS 84 standard.
Coordinates are a pair of numbers named latitude
(or lat
) and longitude
(lng
) and represent decimal degrees i.e. degrees without minutes or seconds, but instead as floating point numbers with fractional part representing fraction of a degree.
latitude
(lat
) values can range from -90.0 to +90.0, and longitude
(lng
) values can range from -180.0 to +180.0.
All the time values that occur in the API (both as query parameters and as returned payload fields) are ISO8601 timestamps according to UTC timezone e.g. 2018-08-24T08:24:48.652Z
.
Average measurements that are calculated for particular period of time (e.g. hourly averages) are returned along with that time period. This period is given as a pair of attributes fromDateTime
and tillDateTime
and represents a left-closed and right-open time interval [fromDateTime, tillDatetime)
.
All the API endpoints support GZIP compression. Enabling compression significantly reduces API response size and can speed up its transfer, and it is a recommended practice.
In order to enable compression an API request should contain additional HTTP header Accept-Encoding: gzip
. Keep in mind that the HTTP client used for communication should be properly configured to handle compressed responses.
curl -X GET -sD - \
--compressed \
--header 'Accept: application/json' \
--header 'Accept-Encoding: gzip' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/measurements/installation?installationId=204'
HTTP/2 200 date: Tue, 18 Sep 2018 13:18:19 GMT content-type: application/json;charset=UTF-8 ... content-encoding: gzip { ... }
Wherever possible, this API will be maintained in a backwards compatible manner.
JSON responses have following stability guarantees:
E.g. an installation that for some reason only measures weather conditions (it may have PM sensor broken) will not return the PM values in its values
array.
When using JSON parsing libraries it is important to configure them in a way to ignore unknown fields, as we may be extending the API and adding new properties to responses without notice.
If it is necessary to change some operations, or its response models in a way that is not backwards compatible, a new endpoint will be created with new response model. We will then maintain both old and new endpoint for an extended period of time, until the old one is safe to be deprecated and dropped. We will inform you if this ever has to occur.
The behaviour of the API may change without warning if the existing behaviour is incorrect or constitutes a security vulnerability.
In the previous API version we were dealing with a concept of a sensor. You could e.g. query what sensors are present in a given area, and then query each individual sensor for its measurements by the sensorId. Unfortunately such approach can lead to some problems. Our devices fleet is constantly growing and it is inevitable that at some point particular device will have to be replaced or moved to another location. When that happens, querying for data by sensorId can lead to incorrect results, since you would be querying for a device that was in fact removed, or replaced.
In the new API version we solve this problem by introducing a concept of installation. An installation is an entity that binds together a sensor and its location where it's installed at a particular time. Thus you can always query an installation safely without risking you would be querying incorrect sensor.
In case a sensor is removed from its location, future queries for its installation can result in either:
301 Moved Permanently
with Location
header pointing to a new url you should query. In addition, a JSON payload is returned: {
"errorCode": "INSTALLATION_REPLACED",
"message": "Installation was replaced with another one",
"details": {
"successorId": <new installation identifier>
}
}
404 Not Found
In principle we recommend using other API endpoints, e.g. those that return measurements by location, or those that return average measurements for a wider area. Then you don't heave to bother with installations, IDs, etc. However, if you still need to query particular installation, just keep in mind the above rules and handle returned codes properly.
An installation sponsor is some sort of organization or institution that purchased our sensor. Usually that would be local authorities (e.g. city or county), but also private companies and individual customers. We have also given away many devices during some campaigns, such as #PolskaOddycha or #KrakówOddycha.
Information about who is the sponsor of particular installation are available on our platform, both in the API and on our map. By presenting particular brand or city on our platform, we kind of want to say "thank you" to our customers for joining the fight for clean air. The API provides: name, logo (url to image file) and a link to sponsor's website.
When presenting our data in a public place (e.g. your application or website) there is no requirement to show who is the sponsor of that installation, although it can't be presented in a way that is vague or misleading. E.g. if you are just displaying an average from multiple installations for the entire city, you should state that clearly.
All the API endpoints that begin with a prefix /v2/measurements
return unified response format that contains measurements from particular installation or area (depending on query parameters). Each response contains fields:
current
- these are measurements data for the last hour (moving average over last 60 minutes)history
- these are historical measurements, currently last 24 full hours that contain average hourly measurements, in ascending orderforecast
- these are measurements forecasts, currently next 24 full hours that contain average anticipated measurements, in ascending orderEach of those fields contains both raw measurement values, as well as indexes calculated using those measurements, and air quality standards violations (where applicable).
When presenting our data in your application it is recommended to indicate clearly what particular data fields mean to avoid any confusion.
Our devices are capable of measuring various types of measurements: temperature, humidity, atmospheric pressure, particulate matter concentrations etc. Our API also gives access to data from other measurement stations (e.g. GIOŚ stations) which measure only some of these parameters (e.g. only PM10). To provide access to all this data in a unified and standard way, measurement data is returned in a dynamic structure in the form of a list (JSON array). The list elements are "name-value" pairs, each for a particular type of measurement.
A list of all types of measurements supported by the API, their names and units used can be retrieved by querying /v2/meta/measurements
.
Air quality index is a way to convert raw measurements data for various pollutants (e.g. dust, gases) into single value representing amount of air pollution. Air quality index usually defines a number from an arbitrary scale (e.g. 0-100), some quantized pollution level (e.g. "Low", "High"), a color representing particular pollution level (e.g. green, red) and sometimes additional text or description e.g. a warning about potential health effects of the pollution.
The goal of air quality index is to present air pollution data in a simple way which is easy to understand by majority of people, especially those unfamiliar with air quality standards and e.g. don't know whether the particular PM10 concentration in the air is safe or not. Secondly, the air quality index reduces data for various pollutants (e.g. dusts, gases) to one value and represents a unified (and somewhat simplified) picture of air quality in a given place, regardless of which air pollutant dominates in a given area.
Air quality indexes are defined by national and international organizations responsible for environmental protection. There exist various indexes in different countries. For example, in the European Union, the CAQI index prepared as part of the CITEAIR project is widely used. In Poland, the Polish Air Quality Index (PIJP) is also defined and calculated by the Chief Inspectorate for Environmental Protection (GIOŚ). Because different indexes have different calculation methods, the same measurement data in a given area can be interpreted as different index values, different pollution levels and be represented by different colors.
The Airly platform currently supports two air quality indexes: CAQI and Polish Air Quality Index - PIJP. The API responses contain the index calculated for the measurement data specified by the indexType
query parameter.
The list of all indexes supported by the platform and the definitions of their levels and ranges with colors can be obtained by querying the address /v2/meta/indexes
.
The highest level of the CAQI index starts at 100 index value and it doesn't show further changes in pollution in intuitive way. Unfortunately, in Poland during the winter season, the level of dust concentrations often exceeds and remains above 100 CAQI on large areas, sometimes around the clock. To better illustrate this problem, we decided to introduce additional CAQI levels with assigned color values. This is how the AirlyCAQI index was created. The AirlyCAQI numeric value is calculated exactly the same as for CAQI, it differs only in the definition of levels for the highest pollution concentrations. It also has more levels (7, while CAQI defines only 5) and colors that are a little nicer, chosen especially for our platform. The AirlyCAQI index is the default index returned by the API.
The air quality standard, also sometimes referred to as a "norm" or a "guideline", is a specific concentration of a concrete component of air pollution, which is considered as an acceptable limit. Pollutants exceeding this value may have negative impact on our health, and the more they exceed the limit the greater the impact may be. Air quality standards are defined by the national and international health organizations, e.g. the World Health Organization (WHO).
In the Airly API, we provide the standard values of PM10 and PM2.5. The API response contains the standard (limit) value and the pollutant concentration expressed as a percentage of the limit.
Operations in group /v2/installations/
allow to search and list available installations and retrieve their metadata, e.g. location, address data etc. All the operations return unified payload response with following attributes:
{
"id": 204,
"location": {
"latitude": 50.062006,
"longitude": 19.940984
},
"address": {
"country": "Poland",
"city": "Kraków",
"street": "Mikołajska",
"number": "4B",
"displayAddress1": "Kraków",
"displayAddress2": "Mikołajska"
},
"elevation": 220.38,
"airly": true,
"sponsor": {
"name": "KrakówOddycha",
"description": "Airly Sensor is part of action",
"logo": "https://cdn.airly.eu/logo/KrakówOddycha.jpg",
"link": "https://sponsor_home_address.pl"
}
}
Endpoint returns single installation metadata, given by installationId
.
Required parameters;
curl -X GET \
--header 'Accept: application/json' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/installations/204'
Endpoint returns list of installations which are closest to a given point, sorted by distance to that point.
Required parameters:
Optional parameters:
curl -X GET \
--header 'Accept: application/json' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/installations/nearest?lat=50.062006&lng=19.940984&maxDistanceKM=5&maxResults=3'
Operations in group /v2/measurements
allow to query measurements data by various criteria. All the operations return unified payload format, which contains following fields:
history
contains data for the last 24 full hours, e.g. if current time is 09:27 UTC, then that field will contain 24 elements, starting with hour interval 09:00-10:00 UTC yesterday, and ending with hour interval 08:00-09:00 UTC today.
The field forecast
contains forecast data for the next 24 full hours, where the first hour is the current hour; e.g. if current time is 09:27 UTC, then that field will contain 24 elements, starting with hour interval 09:00-10:00 UTC today, and ending with hour interval 08:00-09:00 UTC tomorrow.
It is worth noting that the history and forecast fields form an uninterrupted sequence of data for 48 hours, of which the first 24 hours are measured values, and the next 24 hours are forecast values.
The field current
contains averaged measurements for the last 60 minutes (moving average), up until "now", i.e. the time of the request. For example, if the current time is 09:27 UTC, this field contains data for the time interval 08:27-09:27 UTC. However, for installations other than Airly (e.g. GIOŚ stations) this field may contain data up to 3 hours old, due to delays in third party data arrival.
AveragedValues contains:
Accept-Language
request headerAccept-Language
request header
Query parameters common for all endpoints:
{
"current": {
"fromDateTime": "2018-08-24T08:24:48.652Z",
"tillDateTime": "2018-08-24T09:24:48.652Z",
"values": [
{ "name": "PM1", "value": 12.73 },
{ "name": "PM25", "value": 18.7 },
{ "name": "PM10", "value": 35.53 },
{ "name": "PRESSURE", "value": 1012.62 },
{ "name": "HUMIDITY", "value": 66.44 },
{ "name": "TEMPERATURE", "value": 24.71 },
...
],
"indexes": [
{
"name": "AIRLY_CAQI",
"value": 35.53,
"level": "LOW",
"description": "Dobre powietrze.",
"advice": "Możesz bez obaw wyjść na zewnątrz.",
"color": "#D1CF1E"
}
],
"standards": [
{
"name": "WHO",
"pollutant": "PM25",
"limit": 25,
"percent": 74.81
},
...
]
},
"history": [ ... ],
"forecast": [ ... ]
}
Endpoint returns measurements for concrete installation given by installationId
.
In case of the installation does not exist, or it was already deinstalled, returns 404 Not Found
.
In case of the installation was replaced with another device, returns 301 Moved Permanently
with Location
header pointing to new URL of this installation's replacement measurements.
Required parameters:
curl -X GET \
--header 'Accept: application/json' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/measurements/installation?installationId=204'
Endpoint returns measurements for an installation closest to a given location.
If no installation could be found within maxDistanceKM
from the given point, then returns 404 Not Found
.
Required parameters:
Optional parameters:
curl -X GET \
--header 'Accept: application/json' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/measurements/nearest?lat=50.062006&lng=19.940984&maxDistanceKM=5'
Endpoint returns measurements for any geographical location.
Measurement values are interpolated by averaging measurements from nearby sensors (up to 1,5km away from the given point). The returned value is a weighted average, with the weight inversely proportional to the distance from the sensor to the given point.
Required parameters:
curl -X GET \
--header 'Accept: application/json' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/measurements/point?lat=50.062006&lng=19.940984'
Some data types in the API have dynamic nature and their values and ranges can change over time. E.g. in the future our API may support new measurement types (e.g. gases) or new index types used in different countries. Operations in group /v2/meta
return information about current types and values supported by the API.
Endpoint returns a list of all the index types supported in the API along with lists of levels defined per each index type.
The returned array contains objects of type IndexType, which contain following fields:
Accept-Language
request header curl -X GET \
--header 'Accept: application/json' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/meta/indexes'
[
{
"name": "AIRLY_CAQI",
"levels": [
{
"values": "0-25",
"level": "VERY_LOW",
"description": "Very Low",
"color": "#6BC926"
},
...
]
},
...
]
Endpoint returns list of all the measurement types supported in the API along with their names and units.
The returned array contains objects of type MeasurementType which contain following fields:
curl -X GET \
--header 'Accept: application/json' \
--header 'apikey: <apikey>' \
'https://airapi.airly.eu/v2/meta/measurements'
[
{
"name": "PM10",
"label": "PM10",
"unit": "µg/m³"
},
{
"name": "TEMPERATURE",
"label": "Temperature",
"unit": "°C"
},
...
]
We put a lot of effort to make this documentation very clear and understandable. If you feel like there is something we can improve, you can suggest your changes via GitHub. Thanks!