API Endpoints
Complete reference for all QAirbon STAC API endpoints. The API follows the STAC specification v1.0.0 for interoperability with geospatial tools.
Endpoints Overview
Authentication
| Method | Endpoint | Description |
|---|---|---|
| POST | https://keycloak.qairbon.com/realms/QairbonAPIDev/protocol/openid-connect/token |
Get access token via Keycloak OIDC (password or refresh_token grant) |
KPI
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/collections/kpi/items |
Headline figures, one item per industry you can access |
| GET | /api/v1/collections/kpi/items/{industry} |
Headline figures for one industry |
| GET | /api/v1/collections/kpi |
KPI collection metadata, extent of every site you can access |
Sites & Sub-Collections
Panels, QAI & Production
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/collections/panel/items |
List all panels |
| GET | /api/v1/collections/panel/items/{panel_id} |
Get a single panel by ID |
| GET | /api/v1/collections/panel/items/{panel_id}/qai/items |
List QAI observations for a panel |
| GET | /api/v1/collections/panel/items/{panel_id}/qai/items/{qai_id} |
Get a single QAI observation |
| GET | /api/v1/collections/panel/items/{panel_id}/production |
Get production collection metadata for a panel |
| GET | /api/v1/collections/panel/items/{panel_id}/production/items |
List monthly production estimates (kt) for a panel |
| GET | /api/v1/collections/panel/items/{panel_id}/production/items/{item_id} |
Get one month of a panel's production |
Alerts & News
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/collections/alerts |
Get alerts collection metadata |
| GET | /api/v1/collections/alerts/items |
List alert events across accessible sites |
| GET | /api/v1/collections/sites/items/{site_id}/alerts/items |
List alert events for a site's blast furnaces |
| GET | /api/v1/collections/news |
Get news collection metadata |
| GET | /api/v1/collections/news/items |
List press articles across accessible sites |
| GET | /api/v1/collections/sites/items/{site_id}/news/items |
List press articles for a site |
Excel Export
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/export/sites |
Monthly SAI and production of several sites, as an Excel workbook |
| POST | /api/v1/export/panels |
Monthly QAI and production of several panels, as an Excel workbook |
Industries
Every site belongs to one industry, and the industry query parameter of
Sites and Panels takes one of these values. They are also the
ids of the KPI items and the value of the qairbon:industry property.
| Value | Industry | Production measured | Status |
|---|---|---|---|
steel_bf |
Steel — blast furnace | Pig iron | Available |
copper_smelter_primary |
Copper — primary smelter | Copper anode | Beta |
Authentication
The API uses JWT (JSON Web Token) authentication via Keycloak OIDC. Obtain an access token from Keycloak, then include it in subsequent requests.
Get Access Token
Obtain a JWT access token from Keycloak using the password grant. Use the access token in the Authorization: Bearer TOKEN header for all API requests.
application/x-www-form-urlencoded encoding.Request Body (form-encoded)
| Field | Type | Required | Description |
|---|---|---|---|
| grant_type | string | Yes | Use password for direct login, or refresh_token to renew |
| client_id | string | Yes | Always auth (public client, no secret required) |
| username | string | password grant only | Your Keycloak username (email) |
| password | string | password grant only | Your Keycloak password |
| refresh_token | string | refresh grant only | The refresh token from a previous token response |
Example Request
import requests
response = requests.post(
"https://keycloak.qairbon.com/realms/QairbonAPIDev/protocol/openid-connect/token",
data={
"grant_type": "password",
"client_id": "auth",
"username": "YOUR_USERNAME",
"password": "YOUR_PASSWORD",
}
)
response.raise_for_status()
access_token = response.json()["access_token"]
headers = {"Authorization": f"Bearer {access_token}"}
TOKEN=$(curl -s -X POST \
"https://keycloak.qairbon.com/realms/QairbonAPIDev/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&client_id=auth&username=YOUR_USERNAME&password=YOUR_PASSWORD" \
| jq -r '.access_token')
echo "Token: ${TOKEN:0:20}..."
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 300
}
Response Fields
| Field | Type | Description |
|---|---|---|
| access_token | string | JWT token for API requests |
| refresh_token | string | Token for obtaining a new access token when it expires |
| token_type | string | Always "Bearer" |
| expires_in | integer | Access token expiration time in seconds |
Error Responses
| Status | Description |
|---|---|
401 | Invalid credentials |
400 | Invalid grant type or missing required fields |
STAC API Endpoints
All STAC endpoints require authentication using the Authorization: Bearer TOKEN header with a valid access token.
Industry KPIs
One STAC Item per industry you can access, most sites first. Each item carries the number of sites and the capacity they carry, with its geometry set to the bounding envelope of those sites. Use it to fill a summary screen in one request instead of paging the whole site list and reducing it client-side.
datetime is when the data was last updated, that is the most recent observation across the
industry's assets, not when the request ran. An industry that has never been observed carries the time the
figures were derived instead.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Maximum number of items (1-500) |
token | string | Pagination offset |
Example Request
data = requests.get(
"https://${API_HOST}/api/v1/collections/kpi/items",
headers=headers
).json()
for item in data["features"]:
p = item["properties"]
print(p["qairbon:industry"], p["kpi:site_count"], p["kpi:total_capacity"])
curl -H "Authorization: Bearer $TOKEN" \
https://${API_HOST}/api/v1/collections/kpi/items | jq '.features[].properties'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "steel_bf",
"collection": "kpi",
"geometry": { "type": "Polygon", "coordinates": [...] },
"bbox": [-123.02, -46.79, 178.39, 69.32],
"properties": {
"datetime": "2026-09-22T17:17:11Z",
"title": "steel_bf",
"qairbon:industry": "steel_bf",
"kpi:site_count": 398,
"kpi:total_capacity": 1510257.0
},
"links": [
{ "rel": "related", "title": "Sites", "href": ".../collections/sites/items?industry=steel_bf" },
{ "rel": "related", "title": "Panels", "href": ".../collections/panel/items?industry=steel_bf" },
{ "rel": "related", "title": "News", "href": ".../collections/news/items?industry=steel_bf" },
{ "rel": "related", "title": "Alerts", "href": ".../collections/alerts/items?industry=steel_bf" }
]
}
],
"context": { "returned": 3, "limit": 100, "matched": 3 }
}
Get a Single Industry KPI
The same item for one industry. The id is the industry value, for example steel_bf. Returns
404 when you can access no site of that industry, none of them is validated, or none is under thermal
monitoring.
Example Request
item = requests.get(
"https://${API_HOST}/api/v1/collections/kpi/items/steel_bf",
headers=headers
).json()
print(item["properties"]["kpi:site_count"])
curl -H "Authorization: Bearer $TOKEN" \
https://${API_HOST}/api/v1/collections/kpi/items/steel_bf | jq '.properties'
Get KPI Collection
Collection metadata for the KPIs. Its spatial extent is the bounding envelope of every site you can access, including the industries no item reports, so it answers where you have coverage rather than what is measured.
Example Request
col = requests.get(
"https://${API_HOST}/api/v1/collections/kpi",
headers=headers
).json()
print(col["extent"]["spatial"]["bbox"])
curl -H "Authorization: Bearer $TOKEN" \
https://${API_HOST}/api/v1/collections/kpi | jq '.extent'
STAC Catalog
Get the STAC Catalog landing page. Entry point for STAC clients — provides links to collections and search endpoints.
Example Request
catalog = requests.get(
"https://${API_HOST}/api/v1/stac",
headers=headers
).json()
print(catalog["title"])
curl -H "Authorization: Bearer $TOKEN" \
https://${API_HOST}/api/v1/stac | jq '.title'
Response
Returns a StacCatalog object.
{
"type": "Catalog",
"stac_version": "1.0.0",
"id": "qairbon-stac",
"title": "QAirbon STAC API",
"description": "QAirbon STAC API - Industrial sites monitoring data",
"conformsTo": [
"https://api.stacspec.org/v1.0.0/core",
"https://api.stacspec.org/v1.0.0/collections",
"https://api.stacspec.org/v1.0.0/item-search"
],
"links": [
{"rel": "self", "href": "/api/v1/stac"},
{"rel": "root", "href": "/api/v1/stac"},
{"rel": "data", "href": "/api/v1/collections"},
{"rel": "search", "href": "/api/v1/search"}
]
}
List Collections
List all available STAC collections. Returns the "sites" and "panel" collections.
Example Request
data = requests.get(
"https://${API_HOST}/api/v1/collections",
headers=headers
).json()
for col in data["collections"]:
print(col["id"])
curl -H "Authorization: Bearer $TOKEN" \
https://${API_HOST}/api/v1/collections | jq '[.collections[].id]'
Response
{
"collections": [
{
"type": "Collection",
"id": "sites",
"title": "QAirbon Industrial Sites",
"description": "Collection of monitored industrial sites...",
...
}
],
"links": [...]
}
Get Sites Collection
Get metadata for the sites collection, including spatial extent and temporal coverage.
Example Request
collection = requests.get(
"https://${API_HOST}/api/v1/collections/sites",
headers=headers
).json()
print(collection["title"])
print(collection["description"])
curl -H "Authorization: Bearer $TOKEN" \
https://${API_HOST}/api/v1/collections/sites \
| jq '{title, description, keywords}'
Response
Returns a StacCollection object.
{
"type": "Collection",
"stac_version": "1.0.0",
"id": "sites",
"title": "QAirbon Industrial Sites",
"description": "Collection of monitored industrial sites including steel and copper facilities worldwide.",
"license": "proprietary",
"extent": {
"spatial": {"bbox": [[-180, -90, 180, 90]]},
"temporal": {"interval": [[null, null]]}
},
"keywords": ["industrial", "emissions", "monitoring", "steel", "copper"],
"links": [...]
}
List Site Items
List all industrial sites as STAC Items in a GeoJSON FeatureCollection. Results are filtered based on user access permissions and support pagination.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | Optional | Maximum number of results to return (1–10000) |
| token | string | Optional | Pagination token (integer offset) from links[rel=next] |
| industry | string | Optional | Filter by industry: steel_bf or copper_smelter_primary — see Industries |
Example Request
# List steel blast furnace sites
data = requests.get(
"https://${API_HOST}/api/v1/collections/sites/items",
headers=headers,
params={"industry": "steel_bf", "limit": 100}
).json()
print(f"Steel BF plants: {data['context']['matched']}")
for site in data["features"]:
print(f" {site['properties']['name']} ({site['id']})")
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/sites/items?industry=steel_bf&limit=100" \
| jq '.context.matched, [.features[].properties.name]'
Response
Returns a StacFeatureCollection containing StacItem objects.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"stac_version": "1.0.0",
"id": "1d55e91b-65e5-4073-b3ba-d44c394318cc",
"collection": "sites",
"geometry": {"type": "Point", "coordinates": [4.899, 43.435]},
"bbox": [4.899, 43.435, 4.899, 43.435],
"properties": {
"datetime": "2020-01-01T00:00:00Z",
"title": "ArcelorMittal Fos-sur-Mer",
"qairbon:industry": "steel_bf",
"site:status": "operating",
"qairbon:country": "France",
"site:corporates": [...],
"site:inventories": [...],
"site:estimates": [...]
},
"links": [...]
}
],
"links": [...],
"context": {"returned": 1, "limit": 100, "matched": 42}
}
Get Single Site
Retrieve a single industrial site by its ID. Returns detailed information including emissions data, production metrics, and corporate ownership.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| site_id | UUID | Unique identifier of the site |
Example Request
site_id = "1d55e91b-65e5-4073-b3ba-d44c394318cc"
site = requests.get(
f"https://${API_HOST}/api/v1/collections/sites/items/{site_id}",
headers=headers
).json()
print(f"{site['properties']['name']} — {site['properties']['qairbon:country']}")
curl -H "Authorization: Bearer $TOKEN" \
https://${API_HOST}/api/v1/collections/sites/items/1d55e91b-65e5-4073-b3ba-d44c394318cc \
| jq '.properties | {name, country, industry}'
Response
Returns a StacItem object.
Error Responses
| Status | Description |
|---|---|
404 | Site not found or not accessible by user |
Search Sites
Search sites with spatial filters. Supports bounding box and GeoJSON geometry intersection queries.
datetime filter is not applicable — sites have no temporal dimension. The response context only returns returned and limit (no matched total).Request Body
JSON body. See StacSearchRequest model.
| Field | Type | Description |
|---|---|---|
| bbox | array | Bounding box: [west, south, east, north] |
| intersects | GeoJSON | GeoJSON geometry to intersect with site locations |
| limit | integer | Maximum number of results (1–10000) |
Example: Bounding Box Search
# Search near ArcelorMittal Méditerranée, Fos-sur-Mer
results = requests.post(
"https://${API_HOST}/api/v1/search",
headers=headers,
json={
"collections": ["sites"],
"bbox": [4.4, 43.0, 5.4, 43.9],
"limit": 100
}
).json()
print(f"Found {results['context']['returned']} sites")
# Search near ArcelorMittal Méditerranée, Fos-sur-Mer
curl -X POST https://${API_HOST}/api/v1/search \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"collections":["sites"],"bbox":[4.4,43.0,5.4,43.9],"limit":100}' \
| jq '.context.returned'
Example: GeoJSON Intersection
results = requests.post(
"https://${API_HOST}/api/v1/search",
headers=headers,
json={
"intersects": {
"type": "Polygon",
"coordinates": [[
[4.4, 43.0], [5.4, 43.0],
[5.4, 43.9], [4.4, 43.9],
[4.4, 43.0]
]]
},
"limit": 50
}
).json()
print(f"Sites in polygon: {results['context']['returned']}")
curl -X POST https://${API_HOST}/api/v1/search \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"intersects": {
"type": "Polygon",
"coordinates": [[[4.4,43.0],[5.4,43.0],[5.4,43.9],[4.4,43.9],[4.4,43.0]]]
},
"limit": 50
}' | jq '.context.returned'
Response
Returns a StacFeatureCollection.
{
"type": "FeatureCollection",
"features": [...],
"links": [
{"rel": "self", "href": "/api/v1/search"},
{"rel": "root", "href": "/api/v1/stac"}
],
"context": {"returned": 5, "limit": 100}
}
SAI — Site Activity Index
The Site Activity Index (SAI) represents daily thermal activity derived from satellite imagery. Each SAI item is a STAC Feature with a datetime and a quantified index value. SAI is available for sites with thermal_monitoring: true.
List SAI Items
List SAI observations for a site, sorted by date descending.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| datetime | string | No | Date range: 2024-01-01/2024-12-31 (date-only format) |
| limit | integer | No | Max items to return (default: 10) |
| token | string | No | Pagination token from links[rel=next] |
Example Request
site_id = "1d55e91b-65e5-4073-b3ba-d44c394318cc"
data = requests.get(
f"https://${API_HOST}/api/v1/collections/sites/items/{site_id}/sai/items",
headers=headers,
params={"datetime": "2024-01-01/2024-12-31", "limit": 100}
).json()
print(f"SAI records in 2024: {data['context']['matched']}")
for item in data["features"]:
props = item["properties"]
print(f"{props['datetime'][:10]}: SAI={props['sai:value']:.3f}")
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/sites/items/1d55e91b-65e5-4073-b3ba-d44c394318cc/sai/items?datetime=2024-01-01/2024-12-31&limit=100" \
| jq '.context.matched, [.features[] | {date: .properties.datetime[:10], sai: .properties["sai:value"]}]'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"stac_version": "1.0.0",
"id": "a1b2c3d4-...",
"collection": "sai-{site_id}",
"geometry": {"type": "Point", "coordinates": [4.899, 43.435]},
"properties": {
"datetime": "2024-06-15T00:00:00Z",
"sai:value": 0.82,
"sai:version": "2.1"
}
}
],
"links": [...],
"context": {"returned": 100, "limit": 100, "matched": 245}
}
Production — Site Estimated Production
The same monthly observation as SAI, expressed in kilotonnes instead of a dimensionless index: the SAI value multiplied back by the site's nameplate capacity, spread over the month's calendar days. Requires activity access; a site awaiting validation answers 403.
Get Production Collection
STAC Collection describing a site's production series (id production-{site_id}), with its spatial and temporal extent.
List Production Items
One item per SAI month, oldest first. The last page ends with a real-time item (id {site_id}_current) covering the trailing N-day window, N = days in the current month; it is counted in context.matched and filtered by datetime like the others.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| datetime | string | No | Date range: 2024-01-01/2024-12-31 or open-ended 2024-01-01/.. |
| limit | integer | No | Max items to return (1–10000). Omitted: every month is returned |
| token | string | No | Pagination token from links[rel=next] |
Example Request
site_id = "1d55e91b-65e5-4073-b3ba-d44c394318cc"
data = requests.get(
f"https://${API_HOST}/api/v1/collections/sites/items/{site_id}/production/items",
headers=headers,
params={"datetime": "2024-01-01/2024-12-31", "limit": 24}
).json()
for item in data["features"]:
props = item["properties"]
print(f"{props['datetime'][:7]}: {props['production:value']:.1f} kt")
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/sites/items/1d55e91b-65e5-4073-b3ba-d44c394318cc/production/items?datetime=2024-01-01/2024-12-31&limit=24" \
| jq '[.features[] | {month: .properties.datetime[:7], kt: .properties["production:value"]}]'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"stac_version": "1.0.0",
"id": "a1b2c3d4-...",
"collection": "production-{site_id}",
"geometry": {"type": "Point", "coordinates": [4.899, 43.435]},
"properties": {
"datetime": "2024-06-01T00:00:00Z",
"production:value": 268.4,
"production:uncertainty_min": 241.0,
"production:uncertainty_max": 295.2,
"production:active_capacity": 333.3,
"production:version": "2.1",
"qairbon:site_id": "1d55e91b-..."
}
}
],
"links": [...],
"context": {"returned": 12, "limit": 24, "matched": 12}
}
Response Properties
| Property | Type | Description |
|---|---|---|
| production:value | number (kt) | Estimated production for the month |
| production:uncertainty_min / _max | number (kt) | absent | Bounds from the SAI 95% confidence interval |
| production:active_capacity | number (kt/month) | absent | Installed capacity that month: a flat twelfth of the yearly capacity of the assets running, constant between commissioning and retirement events. value / active_capacity is a utilisation that may slightly exceed 1 in 31-day months |
| production:version | string | SAI algorithm version the estimate derives from |
| qairbon:site_id | string (UUID) | Parent site |
| production:last_activity_update | string (ISO 8601) | Acquisition date of the last image used. Real-time item only |
Get a Production Item
One month of a site's production. item_id is the SAI record UUID of that month (the id of a listed item), or {site_id}_current for the real-time estimate. Returns 404 if the record does not exist.
Plume — CO₂ Plume Inversion
CO₂ plume inversions are quantified emission events derived from L2 hyperspectral imagery (e.g., PRISMA, EnMAP). Each plume item includes the emission rate in t/h, uncertainty, and a color-mapped concentration raster. Plume data is available for sites with co2_monitoring: true.
List Plume Items
List CO₂ plume inversion events for a site, sorted by acquisition date descending.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| datetime | string | No | Date range: 2024-01-01/2024-12-31 |
| limit | integer | No | Max items to return (default: 10) |
| token | string | No | Pagination token from links[rel=next] |
Example Request
site_id = "1d55e91b-65e5-4073-b3ba-d44c394318cc"
data = requests.get(
f"https://${API_HOST}/api/v1/collections/sites/items/{site_id}/plume/items",
headers=headers,
params={"limit": 20}
).json()
for item in data["features"]:
props = item["properties"]
print(f"{props['datetime']}: {props['plume:quantification']} t/h ± {props['plume:uncertainty']} t/h")
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/sites/items/1d55e91b-65e5-4073-b3ba-d44c394318cc/plume/items?limit=20" \
| jq '[.features[] | {date: .properties.datetime, rate: .properties["plume:quantification"]}]'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "e5f6a7b8-...",
"collection": "plume-{site_id}",
"geometry": {"type": "MultiPolygon", "coordinates": [...]},
"bbox": [4.85, 43.40, 4.95, 43.47],
"properties": {
"datetime": "2024-06-20T10:32:00Z",
"plume:quantification": 45.3,
"plume:uncertainty": 8.1,
"plume:version": "1.0",
"plume:asset_name": "blast_furnace"
},
"assets": {
"raster": {
"href": "/api/v1/collections/sites/items/{site_id}/plume/items/{plume_id}/assets/raster",
"type": "image/tiff; application=geotiff",
"title": "CO2 Concentration Raster"
}
}
}
],
"links": [...]
}
Get Plume Raster
Returns a 4-band RGBA GeoTIFF (EPSG:4326) with a pre-applied white → deep-red color ramp representing CO₂ concentration. Transparent pixels indicate no-data. The raster is ready to use as an OpenLayers or Leaflet overlay.
ol-stac with displayGeoTiffByDefault: true to display it directly from the STAC item URL.Path Parameters
| Parameter | Type | Description |
|---|---|---|
| site_id | UUID | Unique identifier of the site |
| plume_id | UUID | Unique identifier of the plume item |
Example Request
site_id = "1d55e91b-65e5-4073-b3ba-d44c394318cc"
plume_id = "e5f6a7b8-..."
r = requests.get(
f"https://${API_HOST}/api/v1/collections/sites/items/{site_id}"
f"/plume/items/{plume_id}/assets/raster",
headers=headers
)
with open("plume.tif", "wb") as f:
f.write(r.content)
print("Saved plume.tif")
SITE_ID="1d55e91b-65e5-4073-b3ba-d44c394318cc"
PLUME_ID="e5f6a7b8-..."
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/sites/items/$SITE_ID/plume/items/$PLUME_ID/assets/raster" \
-o plume.tif
Response
Binary GeoTIFF stream — 4-band RGBA, EPSG:4326. Content-Type: image/tiff.
Panel
Panels are logical groupings of industrial sites. Each panel aggregates multiple sites and exposes a combined spatial extent. Panels are used to access QAI (Qairbon Activity Index) data.
List Panels
List all panels accessible to the authenticated user. Use ?industry=steel_bf to filter by industry.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| industry | string | No | Filter by industry: steel_bf or copper_smelter_primary — see Industries |
Example Request
data = requests.get(
"https://${API_HOST}/api/v1/collections/panel/items",
headers=headers,
params={"industry": "steel_bf"}
).json()
for panel in data["features"]:
print(f"Panel: {panel['properties']['title']} ({panel['id']})")
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/panel/items?industry=steel_bf" \
| jq '[.features[] | {id, name: .properties.name}]'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "f1a2b3c4-...",
"properties": {
"datetime": "2020-01-01T00:00:00Z",
"title": "Steel BF World Panel",
"panel:site_count": 412,
"qairbon:industry": "steel_bf",
"panel:type": "default"
}
}
],
"context": {"returned": 1, "limit": 10, "matched": 1}
}
QAI — Qairbon Activity Index
The Qairbon Activity Index (QAI) is a production-weighted aggregation of SAI values from all sites within a panel. Each QAI item represents a monthly aggregated activity index for the panel as a whole.
List QAI Items
List monthly QAI observations for a panel, sorted by date descending.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| datetime | string | No | Date range: 2023-01-01/2024-12-31 (date-only format) |
| limit | integer | No | Max items to return (default: 10) |
Example Request
# Get first steel_bf panel and list its QAI
panels = requests.get(
"https://${API_HOST}/api/v1/collections/panel/items",
headers=headers,
params={"industry": "steel_bf"}
).json()
panel_id = panels["features"][0]["id"]
data = requests.get(
f"https://${API_HOST}/api/v1/collections/panel/items/{panel_id}/qai/items",
headers=headers,
params={"datetime": "2023-01-01/2024-12-31", "limit": 100}
).json()
for item in data["features"]:
props = item["properties"]
print(f"{props['datetime'][:7]}: QAI={props['qai:value']:.3f}")
PANEL_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/panel/items?industry=steel_bf" \
| jq -r '.features[0].id')
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/panel/items/$PANEL_ID/qai/items?datetime=2023-01-01/2024-12-31&limit=100" \
| jq '[.features[] | {month: .properties.datetime[:7], qai: .properties["qai:value"]}]'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "2024-06",
"collection": "qai-{panel_id}",
"properties": {
"datetime": "2024-06-01T00:00:00Z",
"qai:value": 0.74,
"qai:version": "1.0",
"qairbon:panel_id": "f1a2b3c4-...",
"qai:site_count": 18
}
}
],
"context": {"returned": 24, "limit": 100, "matched": 24}
}
Production — Panel Estimated Production
The QAI counterpart in kilotonnes: for each month, the sum over the panel's sites of their estimated production, each scaled by the site's attribution share in the panel. Requires activity access.
Get Panel Production Collection
STAC Collection describing a panel's production series (id panel-{panel_id}-production), with its spatial and temporal extent.
List Panel Production Items
One item per month (id {panel_id}_{YYYY-MM}), oldest first. The last page ends with a real-time item (id {panel_id}_current) covering the trailing N-day window, N = days in the current month.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| datetime | string | No | Date range: 2023-01-01/2024-12-31 or open-ended 2023-01-01/.. |
| limit | integer | No | Max items to return (1–10000). Omitted: every month is returned |
| token | string | No | Pagination token from links[rel=next] |
Example Request
panel_id = "f1a2b3c4-..."
data = requests.get(
f"https://${API_HOST}/api/v1/collections/panel/items/{panel_id}/production/items",
headers=headers,
params={"datetime": "2023-01-01/..", "limit": 24}
).json()
for item in data["features"]:
props = item["properties"]
print(f"{props['datetime'][:7]}: {props['production:value']:.0f} kt ({props['production:site_count']} sites)")
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/panel/items/$PANEL_ID/production/items?datetime=2023-01-01/..&limit=24" \
| jq '[.features[] | {month: .properties.datetime[:7], kt: .properties["production:value"]}]'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"stac_version": "1.0.0",
"id": "f1a2b3c4-..._2024-06",
"collection": "panel-{panel_id}-production",
"geometry": {"type": "Polygon", "coordinates": [...]},
"properties": {
"datetime": "2024-06-01T00:00:00Z",
"production:value": 4820.5,
"production:uncertainty_min": 4410.2,
"production:uncertainty_max": 5233.8,
"production:active_capacity": 6150.0,
"production:version": "2.1",
"qairbon:panel_id": "f1a2b3c4-...",
"production:site_count": 18
}
}
],
"links": [...],
"context": {"returned": 24, "limit": 24, "matched": 33}
}
Response Properties
Same as the site production properties, with qairbon:panel_id instead of qairbon:site_id, no production:last_activity_update, and:
| Property | Type | Description |
|---|---|---|
| production:site_count | integer | Number of sites with a nameplate capacity contributing to the value |
Get a Panel Production Item
One month of a panel's production. item_id is {panel_id}_{YYYY-MM}, or {panel_id}_current for the real-time estimate. Returns 404 if the record does not exist.
Alerts — Restart / Shutdown Events
Alerts are restart and shutdown events detected on blast furnaces from satellite thermal monitoring. Each item is geo-referenced on the blast furnace centroid and carries a level (suspected or confirmed), a detection probability and a qualitative confidence level. Alerts are attached to an asset, not a site: the per-site endpoint aggregates every blast furnace of the site.
403. Site items expose a related link titled Site Alerts only when at least one alert is visible to you.List Alert Items
List alert events across every site you can access, most recent event first.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| datetime | string | No | ISO 8601 interval on the item datetime: 2026-07-01/.., ../2026-07-31 or start/end |
| limit | integer | No | Max items to return (default: 100, max: 10000) |
| token | string | No | Pagination offset, taken from the next link |
Example Request
data = requests.get(
"https://${API_HOST}/api/v1/collections/alerts/items",
headers=headers,
params={"datetime": "2026-07-01/..", "limit": 50}
).json()
for alert in data["features"]:
p = alert["properties"]
print(p["datetime"][:10], p["title"], p["alert:event"], p["alert:level"])
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/alerts/items?datetime=2026-07-01/..&limit=50" \
| jq '[.features[] | {date: .properties.datetime[:10], site: .properties.title, event: .properties["alert:event"]}]'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "0190a1b2-...",
"collection": "alerts",
"geometry": { "type": "Point", "coordinates": [139.95, 35.35] },
"properties": {
"datetime": "2026-07-28T00:00:00Z",
"title": "Kimitsu Works",
"alert:processing_date": "2026-07-30T03:00:00Z",
"alert:level": "confirmed",
"alert:event": "shutdown",
"alert:detection_probability": 0.91,
"alert:qualitative_cl": "high",
"qairbon:asset_id": "7c3e...",
"qairbon:site_id": "6af8...",
"qairbon:country": "Japan"
}
}
],
"context": {"returned": 1, "limit": 100, "matched": 42}
}
List Alert Items for a Site
List the alert events of every blast furnace attached to one site. Same item shape and parameters as the global list. Returns 404 when the site does not exist or is outside your scope.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| datetime | string | No | ISO 8601 interval on the item datetime: 2026-07-01/.., ../2026-07-31 or start/end |
| limit | integer | No | Max items to return (default: 100, max: 10000) |
| token | string | No | Pagination offset, taken from the next link |
Example Request
site_id = "YOUR_SITE_ID"
data = requests.get(
f"https://${API_HOST}/api/v1/collections/sites/items/{site_id}/alerts/items",
headers=headers,
).json()
print(data["context"]["matched"], "alerts")
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/sites/items/$SITE_ID/alerts/items" \
| jq '.context.matched'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "0190a1b2-...",
"collection": "alerts",
"geometry": { "type": "Point", "coordinates": [139.95, 35.35] },
"properties": {
"datetime": "2026-07-28T00:00:00Z",
"title": "Kimitsu Works",
"alert:processing_date": "2026-07-30T03:00:00Z",
"alert:level": "confirmed",
"alert:event": "shutdown",
"alert:detection_probability": 0.91,
"alert:qualitative_cl": "high",
"qairbon:asset_id": "7c3e...",
"qairbon:site_id": "6af8...",
"qairbon:country": "Japan"
}
}
],
"context": {"returned": 1, "limit": 100, "matched": 42}
}
News — Press Articles
News items are press articles from trade publishers that mention a monitored site, collected daily. Each item is one (article, site) pair: an article naming several plants yields one item per plant. The geometry is the site footprint, the headline is title, the verbatim quote is description and the publication date is datetime. A via link points to the source article.
related link titled Site News only when at least one article exists.List News Items
List press articles across every site you can access, most recently published first.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| datetime | string | No | ISO 8601 interval on the item datetime: 2026-07-01/.., ../2026-07-31 or start/end |
| limit | integer | No | Max items to return (default: 100, max: 10000) |
| token | string | No | Pagination offset, taken from the next link |
| category | string | No | One of production_capacity, prices_markets, investment_ma, decarbonisation_tech, regulation_policy, incident_outage, labour, trade_tariffs, financial_results |
Example Request
data = requests.get(
"https://${API_HOST}/api/v1/collections/news/items",
headers=headers,
params={"datetime": "2026-09-01/..", "category": "production_capacity"}
).json()
for item in data["features"]:
p = item["properties"]
print(p["datetime"][:10], p["news:site_name"], p["title"], p["news:url"])
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/news/items?datetime=2026-09-01/..&category=production_capacity" \
| jq '[.features[] | {date: .properties.datetime[:10], site: .properties["news:site_name"], title: .properties.title}]'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "0190c3d4-...",
"collection": "news",
"geometry": { "type": "Polygon", "coordinates": [...] },
"properties": {
"datetime": "2026-09-07T10:00:00Z",
"title": "Nippon Steel idles Kimitsu blast furnace",
"description": "Kimitsu No. 2 blast furnace was idled for relining",
"news:processing_date": "2026-09-08T03:00:00Z",
"news:source": "Kallanish",
"news:category": "production_capacity",
"news:url": "https://www.kallanish.com/...",
"news:mechanism": "rss",
"news:site_name": "Kimitsu Works",
"qairbon:site_id": "6af8...",
"qairbon:country": "Japan"
},
"links": [
{"rel": "related", "href": "/api/v1/collections/sites/items/6af8...", "title": "Mentioned site"},
{"rel": "via", "href": "https://www.kallanish.com/...", "title": "Source article"}
]
}
],
"context": {"returned": 1, "limit": 100, "matched": 12}
}
List News Items for a Site
List the press articles mentioning one site. Same item shape and parameters as the global list. Returns 404 when the site does not exist or is outside your scope.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| datetime | string | No | ISO 8601 interval on the item datetime: 2026-07-01/.., ../2026-07-31 or start/end |
| limit | integer | No | Max items to return (default: 100, max: 10000) |
| token | string | No | Pagination offset, taken from the next link |
| category | string | No | One of production_capacity, prices_markets, investment_ma, decarbonisation_tech, regulation_policy, incident_outage, labour, trade_tariffs, financial_results |
Example Request
site_id = "YOUR_SITE_ID"
data = requests.get(
f"https://${API_HOST}/api/v1/collections/sites/items/{site_id}/news/items",
headers=headers,
).json()
for item in data["features"]:
print(item["properties"]["title"])
curl -H "Authorization: Bearer $TOKEN" \
"https://${API_HOST}/api/v1/collections/sites/items/$SITE_ID/news/items" \
| jq '[.features[].properties.title]'
Response
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "0190c3d4-...",
"collection": "news",
"geometry": { "type": "Polygon", "coordinates": [...] },
"properties": {
"datetime": "2026-09-07T10:00:00Z",
"title": "Nippon Steel idles Kimitsu blast furnace",
"description": "Kimitsu No. 2 blast furnace was idled for relining",
"news:processing_date": "2026-09-08T03:00:00Z",
"news:source": "Kallanish",
"news:category": "production_capacity",
"news:url": "https://www.kallanish.com/...",
"news:mechanism": "rss",
"news:site_name": "Kimitsu Works",
"qairbon:site_id": "6af8...",
"qairbon:country": "Japan"
},
"links": [
{"rel": "related", "href": "/api/v1/collections/sites/items/6af8...", "title": "Mentioned site"},
{"rel": "via", "href": "https://www.kallanish.com/...", "title": "Source article"}
]
}
],
"context": {"returned": 1, "limit": 100, "matched": 12}
}
Excel Export
Download SAI or QAI time series, with the matching production, as Excel workbooks, several sites or panels in one file.
Export Sites
Download the monthly SAI and production of several sites as one Excel
workbook (.xlsx) with two sheets, SAI and Production (kt). Each
sheet has one row per completed month, then a Current row with the real-time value, and per
site the columns SAI (or Production (kt)), Unc. Min,
Unc. Max under a header row naming the site. Requires activity access.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| site_ids | array of string | Yes | Site ids, as returned by List Sites. Ids you cannot access, or that are not valid UUIDs, are skipped. |
| start_date | string (ISO 8601) | No | First day of the range. Default: one year before today. |
| end_date | string (ISO 8601) | No | Last day of the range. Default: today. |
start_date: the default range starts mid-month, so its first month
comes out empty. The current month never gets a monthly row — its value is not final until the month
ends — and the Current row appears only when the real-time value was measured within the
range. Every month of the range gets a row, empty when there is no value, and the column groups
follow the API's order, not the order of the ids sent.Example
response = requests.post(
"https://${API_HOST}/api/v1/export/sites",
headers=headers,
json={
"site_ids": ["ac7b2712-a83a-4ce8-b459-0a68befa7f19"],
"start_date": "2025-10-01T00:00:00Z",
"end_date": "2026-09-30T00:00:00Z",
},
)
response.raise_for_status()
with open("sites.xlsx", "wb") as f:
f.write(response.content)
curl -s -X POST "https://${API_HOST}/api/v1/export/sites" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"site_ids": ["ac7b2712-a83a-4ce8-b459-0a68befa7f19"], "start_date": "2025-10-01T00:00:00Z", "end_date": "2026-09-30T00:00:00Z"}' \
-o sites.xlsx
Response
200 with the workbook as the body
(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet), named in
Content-Disposition after the range, e.g. sites_202510_202609.xlsx.
More examples: Excel Export.
Error Responses
| Status | Description |
|---|---|
404 | No accessible site left among the ids sent — or the account has no activity access |
422 | site_ids missing, or a date that is not ISO 8601 |
Export Panels
Download the monthly QAI and production of several panels as one Excel
workbook (.xlsx) with two sheets, QAI and Production (kt), laid
out like the site export: one row per completed month, then a Current row, and per panel
the columns QAI (or Production (kt)), Unc. Min,
Unc. Max. Requires activity access.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| panel_ids | array of string | Yes | Panel ids, as returned by List Panels. Ids you cannot access, or that are not valid UUIDs, are skipped. |
| start_date | string (ISO 8601) | No | First day of the range. Default: one year before today. |
| end_date | string (ISO 8601) | No | Last day of the range. Default: today. |
start_date: the default range starts mid-month, so its first month
comes out empty. The current month never gets a monthly row — its value is not final until the month
ends — and the Current row appears only when the real-time value was measured within the
range. Every month of the range gets a row, empty when there is no value, and the column groups
follow the API's order, not the order of the ids sent.Example
response = requests.post(
"https://${API_HOST}/api/v1/export/panels",
headers=headers,
json={
"panel_ids": ["a9884bf4-cdc1-5bf1-b906-b7e27e13797e"],
"start_date": "2025-10-01T00:00:00Z",
"end_date": "2026-09-30T00:00:00Z",
},
)
response.raise_for_status()
with open("panels.xlsx", "wb") as f:
f.write(response.content)
curl -s -X POST "https://${API_HOST}/api/v1/export/panels" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"panel_ids": ["a9884bf4-cdc1-5bf1-b906-b7e27e13797e"], "start_date": "2025-10-01T00:00:00Z", "end_date": "2026-09-30T00:00:00Z"}' \
-o panels.xlsx
Response
200 with the workbook as the body
(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet), named in
Content-Disposition after the range, e.g. panels_202510_202609.xlsx.
More examples: Excel Export.
Error Responses
| Status | Description |
|---|---|
404 | No accessible panel left among the ids sent — or the account has no activity access |
422 | panel_ids missing, or a date that is not ISO 8601 |