Authentication
Every /api/ request needs an access token. You get one by exchanging your QAirbon username and
password for a token, then sending that token on each request. Tokens last one hour.
Get a token
Post your username and password to the token endpoint. Replace {{USER_NAME}} and
{{PASSWORD}} with your own credentials. No client secret is needed.
cURL
curl --location 'https://keycloak.qairbon.com/realms/QairbonAPIDev/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=auth' \
--data-urlencode 'username={{USER_NAME}}' \
--data-urlencode 'password={{PASSWORD}}'
Python
import requests
TOKEN_URL = "https://keycloak.qairbon.com/realms/QairbonAPIDev/protocol/openid-connect/token"
response = requests.post(TOKEN_URL, data={
"grant_type": "password",
"client_id": "auth",
"username": "you@example.com",
"password": "your-password",
})
response.raise_for_status()
token = response.json()["access_token"]
JavaScript
const TOKEN_URL =
"https://keycloak.qairbon.com/realms/QairbonAPIDev/protocol/openid-connect/token";
const response = await fetch(TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "password",
client_id: "auth",
username: "you@example.com",
password: "your-password",
}),
});
const { access_token } = await response.json();
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Use the token
Send it as a Bearer token in the Authorization header of every API request.
cURL
curl -H "Authorization: Bearer $TOKEN" \
https://${API_HOST}/api/${API_VERSION}/collections/kpi/items
Python
headers = {"Authorization": f"Bearer {token}"}
data = requests.get(
"https://${API_HOST}/api/${API_VERSION}/collections/kpi/items",
headers=headers,
).json()
JavaScript
const data = await fetch(
"https://${API_HOST}/api/${API_VERSION}/collections/kpi/items",
{ headers: { Authorization: `Bearer ${access_token}` } }
).then(r => r.json());
Refresh the token
An access token expires after one hour. The token response also gives you a refresh_token, which
buys a new access token without sending your password again.
curl --location 'https://keycloak.qairbon.com/realms/QairbonAPIDev/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'client_id=auth' \
--data-urlencode 'refresh_token={{REFRESH_TOKEN}}'
The response has the same shape as the one above, with a fresh access_token and a new
refresh_token. If the refresh token has itself expired, ask for a token with your password again.
What your role changes
The token carries your role, and the API answers accordingly: you only ever see the sites your account is entitled to, and some figures are counted differently.
| Role | What it sees |
|---|---|
user | The validated sites of its own portfolio |
owner | The same, with extended management rights on its own sites |
validator | Also the sites still awaiting validation, which it reviews |
admin | Everything, including unvalidated sites and unpublished alerts |
kpi:site_count only for validator and admin.Endpoints that need no token
The documentation endpoints are open:
| Endpoint | Description |
|---|---|
/api/${API_VERSION}/docs | Swagger UI |
/api/${API_VERSION}/redoc | ReDoc reference |
/api/${API_VERSION}/openapi.json | OpenAPI specification |
/api/${API_VERSION}/openapi.yaml | OpenAPI specification (YAML) |
Every other route under /api/ needs a token.
Errors
401 from the token endpoint
Your username or password is wrong, or the account is disabled.
{
"error": "invalid_grant",
"error_description": "Invalid user credentials"
}
401 from the API
The Authorization header is missing, malformed, or the token has expired. Get a new one.
{
"detail": "Not authenticated"
}
403 from the API
The token is valid but your account is not entitled to that data, for example the activity figures behind SAI and alerts.
{
"detail": "No activity access"
}
Good practice
- Keep the password out of your code. Read it from an environment variable or a prompt, not a committed file.
- Reuse the token for its hour. Requesting a new one on every call is slow and needless.
- Refresh rather than re-authenticate while the refresh token is still valid.
- Never log a token. Anyone holding it has your access until it expires.
- Always use HTTPS, so neither the password nor the token travels in the clear.