Quickstart
This walkthrough takes you from zero to a working integration in five steps. By the end you will have fetched a token, listed your stations, and pulled the latest measurement off one of them.
Every command assumes you have these four environment variables set:
export TOKEN_URL="https://auth.persium.example.com/realms/persium/protocol/openid-connect/token"
export API_BASE_URL="https://api.persium.co.uk/api/m2m/v1"
export CLIENT_ID="acme-corp-backend"
export CLIENT_SECRET="••••••••••"
Step 1 — Fetch an access token
POST to the token endpoint with grant_type=client_credentials.
curl -s -X POST "$TOKEN_URL" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET"
You get back the access token and its lifetime in seconds:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"expires_in": 300,
"token_type": "Bearer",
"scope": "email profile"
}
Capture the token into a variable for the rest of this walkthrough:
export ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIs..."
See Authentication for token caching and refresh.
Step 2 — Verify your access
GET /me is the cheapest way to confirm credentials work and to see
which organisation you are scoped to.
curl -s "$API_BASE_URL/me" \
-H "Authorization: Bearer $ACCESS_TOKEN"
{
"client_id": "acme-corp-backend",
"organisation_uuid": "8d2f1a3e-1234-4c1b-9999-abcdefabcdef",
"label": "Acme Corp production backend",
"all_organisations": false
}
A 401 here means the token is wrong — see Troubleshooting.
Step 3 — List your stations
curl -s "$API_BASE_URL/stations?page=1&per_page=20" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Response shape:
{
"data": [
{
"uuid": "11111111-2222-3333-4444-555555555555",
"name": "Station Alpha",
"latitude": 51.5074,
"longitude": -0.1278,
"status": "online",
"last_updated": "2026-05-19T10:23:00Z",
"daqi": { "value": 4, "base_on": "PM25" },
"caqi": { "value": 38, "base_on": "PM25" },
"usaqi": { "value": 75, "base_on": "PM25", "color": "#FFFF00" }
}
],
"pagination": { "page": 1, "per_page": 20, "total": 47 }
}
Defaults: page=1, per_page=20. Maximum per_page is 200. See
List Stations for the full schema.
Step 4 — Inspect a station
Pick a UUID from the previous response and store it:
export STATION_UUID="11111111-2222-3333-4444-555555555555"
Fetch the station and its sensors:
curl -s "$API_BASE_URL/stations/$STATION_UUID" \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl -s "$API_BASE_URL/stations/$STATION_UUID/sensors" \
-H "Authorization: Bearer $ACCESS_TOKEN"
A 404 means the station does not exist or belongs to another
organisation — Persium does not distinguish between the two on purpose.
Step 5 — Fetch measurements
The simplest case — single most recent record:
curl -s "$API_BASE_URL/stations/$STATION_UUID/measurements/latest" \
-H "Authorization: Bearer $ACCESS_TOKEN"
For historical data, pass an explicit time window in ISO 8601 (UTC):
curl -s "$API_BASE_URL/stations/$STATION_UUID/measurements?\
start_time=2026-05-01T00:00:00Z&\
end_time=2026-05-19T00:00:00Z&\
page=1&per_page=500" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Defaults: page=1, per_page=100. Maximum per_page is 1000.
:::tip Always pass a time window
Omitting start_time/end_time returns the most recent rows
regardless of date, which is rarely what you want for historical
analysis.
:::
You're done
That's the full integration surface. Recommended next reading:
- Authentication — token caching and refresh.
- Errors — full status code matrix.
- Security — secret handling and rotation.