Getting Started with the Airthings API
Airthings API enables developers and partners to get access to the data of their devices, for use outside of the Airthings Platform.
Building apps
To be able to pull data from the Airthings cloud to your app, it needs to be registered with Airthings. Register new app. Once created, your app can:
- List your devices
- List your segments
- Read your device sample's data
- Add/register a new device
The read device data scope allows access to seeing data from the /devices
endpoints.
If you want to write/post to the API, the write device scope should be selected.
Authorizing an app to read my device data (OAuth2 Code Flow)
Once your app has been registered in our cloud (with Code Flow selected as Flow type), it is possible for other users to access their data through it. To allow an app to read data on your behalf, you need to authorize it.
To enable this we have implemented the OAuth2 Code Flow.
The user of the app should be redirected to accounts.airthings.com/authorize?client_id={appClientId}&redirect_uri={appRedirectUri} for authorization:
- Sign in with their Airthings credentials
- Authorize the app to read data on their behalf Once this is complete, the user is redirected from the accounts app back to the app located at the url from: redirect_uri with an authorization code. The authorization code in turn can be exchanged for an access_token on the /token endpoint.
- Authorization URL: https://accounts.airthings.com/authorize
- Token URL: https://accounts-api.airthings.com/v1/token
After authorize the token endpoint is to be called with a payload similar to:
{
"grant_type": "authorization_code",
"client_id": "b8a19a7d-64cd-4281-xxxx-3ffacc726fe3",
"client_secret": "82bfed8b-793a-4423-xxxx-7491303354a0",
"code": "auth-code-from-authorize",
"redirect_uri": "https://redirect.configured.for.client"
}
Refreshing the access_token
The access-token has a short life-time (seconds can be seen in the token response expires_in
). Using an expired access-token towards the API will result in an error response with status code 401, signaling a refresh is needed.
When using the code flow the access-token can be refreshed by using a refresh-token.
Upon refresh (and code_grant
) a new refresh-token is provided. It is important to store the new refresh-token upon refresh, since this token also has a fixed life-time.
To refresh the access-token, the token endpoint: https://accounts-api.airthings.com/v1/token is called with a refresh-grant payload:
{
"grant_type": "refresh_token",
"client_id": "b8a19a7d-64cd-4281-xxxx-3ffacc726fe3",
"client_secret": "82bfed8b-793a-4423-xxxx-7491303354a0",
"refresh_token": "eyJhbGciOiJSUzI1NiJ9.eyJ....."
}
Using an expired refresh-token yields an error response with a status code 4xx.
Note: in Client Credentials Grant (below) no refresh_token
is provided. Hence, there no refreshing is possible.
Authenticating a machine to read data (OAuth2 Client Credentials Grant)
Especially for machine-to-machine (m2m) authentication we have implemented Client Credentials from the OAuth2 spec.
NOTE: it is not recommended to use this flow for frontend authentication to the API, only backends can maintain the integrity of a client secret.
To setup a client for m2m, create a client for your app, as described above (configured for Client Credentials as Flow type).
Next use the client id and client secret to request a token from the accounts-api. The scope specifies what type of operation you want to use.
For the date fetching endpoints, the correct scope would be the read:device
scope. For writing/posting, use the write:device
scope.
- Token URL: https://accounts-api.airthings.com/v1/token
{
"grant_type":"client_credentials",
"client_id":"b8a19a7d-64cd-4281-xxxx-3ffacc726fe3",
"client_secret":"82bfed8b-793a-4423-xxxx-7491303354a0",
"scope": ["read:device"]
}
The token from the response can be used to access the endpoints in the API until it expires.
After the token is expired (time for expires_in
is provided in seconds in the response) the token endpoint has to be called again.
Using an expired access-token will result in an error response with status code 401.
API-Documentation
All currently available endpoints are documented on API-Docs.
Rate Limiting
Each Airthings For Business Client is by default allowed 5000 requests per hour. Users signed in through the same client all share that same quota. Since the client and subscription is owned by the user administrating it.
The current rate limit status is indicated in the response headers:
X-RateLimit-Reset: 1607336100 // The time at which the current rate limit window resets (UTC epoch seconds).
X-RateLimit-Remaining: 1000 // The number of remaining requests in the current rate limit window.
X-RateLimit-Limit: 5000 // The maximum number of requests you're granted per hour.
If a client exceeds the rate limit, the API will respond with status code: 429
and the below mentioned response body and header indicating when the sliding windows will grant the client a new request.
X-RateLimit-Retry-After: 100 // A new request can be performed after this many seconds.
{
"error": "TOO_MANY_REQUESTS",
"error_description": "Rate limit on API exceeded"
}
You can request increases to the API quota:
Airthings For Business Support
Postman / Insomnia
Download the api specification here for import into Postman or Insomnia.
Right click the newly imported collection and select Edit to configure the collection for Oauth 2.0 Authorization. Getting a token for the API can either be done through Code Flow or Client Credentials Flow, select based on your use case:
- Authorization Code Flow : Authorization for users. Multiple users will be able to login and fetch data for their accounts on airthings.
- Client Credentials Flow : Authorization for a machine, i.e. a backend integration. Only one account will be used in the integration.
Authorization Code Flow
The client id, secret and redirect_uri for your client needs to be added from the clients page.
Now you should be able to get an access token that can be used by the collection, by pressing Use Token.
Client Credentials Flow
The client id, secret for your client needs to be added from the clients page.
Finalizing Postman setup
In the collection set the request authorization to Inherit auth from parent (or Oauth 2.0).
Sending the requests should now succeed as long as the access token is valid (1 hour, in Code Flow: the refresh_token is then to be used to get a new access_token, in Client Credentials Flow: the client_id, client_secret is used to fetch a new token).
Code samples
Authentication - Authorization (Code Flow)
'use strict';
const simpleOauthModule = require('simple-oauth2');
const app = require('express')();
const port = 3000;
const createApplication = (cb) => {
const callbackUrl = 'http://localhost:3000/callback';
app.listen(port, (err) => {
if (err) return console.error(err);
console.log(`Express server listening at http://localhost:${port}`);
cb({app, callbackUrl});
});
};
createApplication(({ app, callbackUrl }) => {
const oauth2 = simpleOauthModule.create({
client: {
id: 'INSERT CLIENT_ID HERE',
secret: 'INSERT CLIENT_SECRET HERE',
},
auth: {
tokenHost: 'https://accounts.airthings.com',
tokenPath: 'https://accounts-api.airthings.com/v1/token',
authorizePath: '/authorize',
},
options: {
authorizationMethod: 'body',
}
});
// Authorization uri definition
const authorizationUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: callbackUrl,
scope: 'read:device',
});
// Initial page redirecting to Airthings
app.get('/auth', (req, res) => {
console.log(authorizationUri);
res.redirect(authorizationUri);
});
// Callback service parsing the authorization token and asking for the access token
app.get('/callback', async (req, res) => {
const code = req.query.code;
const options = {
code,
redirect_uri: callbackUrl,
};
try {
const result = await oauth2.authorizationCode.getToken(options);
const tokens = oauth2.accessToken.create(result);
return res.status(200).json(tokens);
} catch(error) {
console.error('Access Token Error', error.message);
return res.status(500).json('Authentication failed');
}
});
app.get('/', (req, res) => {
res.send('Hello<br><a href="/auth">Log in with Airthings</a>');
});
});
'use strict';
const simpleOauthModule = require('simple-oauth2');
const Wreck = require('@hapi/wreck');
const app = require('express')();
const port = 3000;
const createApplication = (cb) => {
const callbackUrl = 'http://localhost:3000/callback';
app.listen(port, (err) => {
if (err) return console.error(err);
console.log(`Express server listening at http://localhost:${port}`);
cb({
app,
callbackUrl,
});
});
};
const getDevices = async function (token) {
const options = {
headers: {'Authorization': token.access_token}
};
const { res, payload } = await Wreck.get('https://ext-api.airthings.com/v1/devices', options);
return JSON.parse(payload.toString());
};
createApplication(({ app, callbackUrl }) => {
const oauth2 = simpleOauthModule.create({
client: {
id: 'INSERT CLIENT_ID HERE',
secret: 'INSERT CLIENT_SECRET HERE',
},
auth: {
tokenHost: 'https://accounts.airthings.com',
tokenPath: 'https://accounts-api.airthings.com/v1/token',
authorizePath: '/authorize',
},
options: {
authorizationMethod: 'body',
}
});
// Authorization uri definition
const authorizationUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: callbackUrl,
scope: 'read:device',
});
// Initial page redirecting to Airthings
app.get('/auth', (req, res) => {
console.log(authorizationUri);
res.redirect(authorizationUri);
});
// Callback service parsing the authorization token and asking for the access token
app.get('/callback', async (req, res) => {
const code = req.query.code;
const options = {
code,
redirect_uri: callbackUrl,
};
try {
const result = await oauth2.authorizationCode.getToken(options);
const tokens = oauth2.accessToken.create(result);
const devices = await getDevices(tokens.token);
return res.status(200).json(devices);
} catch(error) {
console.error('Access Token Error', error.message);
return res.status(500).json('Authentication failed');
}
});
app.get('/', (req, res) => {
res.send('Hello<br><a href="/auth">Log in with Airthings</a>');
});
});