API Endpoints

Complete reference for all QAirbon STAC API endpoints. The API follows the STAC specification v1.0.0 for interoperability with geospatial tools.

Language:

Endpoints Overview

Authentication

MethodEndpointDescription
POST https://keycloak.qairbon.com/realms/QairbonAPIDev/protocol/openid-connect/token Get access token via Keycloak OIDC (password or refresh_token grant)

KPI

MethodEndpointDescription
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

MethodEndpointDescription
GET /api/v1/stac STAC Catalog landing page
GET /api/v1/collections List all collections
GET /api/v1/collections/sites Get sites collection metadata
GET /api/v1/collections/sites/items List all site items (paginated)
GET /api/v1/collections/sites/items/{site_id} Get a single site by ID
GET /api/v1/collections/sites/items/{site_id}/sai/items List SAI observations for a site
GET /api/v1/collections/sites/items/{site_id}/sai/items/{sai_id} Get a single SAI observation
GET /api/v1/collections/sites/items/{site_id}/production Get production collection metadata for a site
GET /api/v1/collections/sites/items/{site_id}/production/items List monthly production estimates (kt) for a site
GET /api/v1/collections/sites/items/{site_id}/production/items/{item_id} Get one month of a site's production
GET /api/v1/collections/sites/items/{site_id}/plume/items List CO₂ plume items for a site
GET /api/v1/collections/sites/items/{site_id}/plume/items/{plume_id}/assets/raster Get color-mapped plume concentration raster (GeoTIFF RGBA)
POST /api/v1/search Search sites with spatial filters

Panels, QAI & Production

MethodEndpointDescription
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

MethodEndpointDescription
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

MethodEndpointDescription
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.

ValueIndustryProduction measuredStatus
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

POST https://keycloak.qairbon.com/realms/QairbonAPIDev/protocol/openid-connect/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.

i
This endpoint does not require authentication. It uses application/x-www-form-urlencoded encoding.

Request Body (form-encoded)

FieldTypeRequiredDescription
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

FieldTypeDescription
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

StatusDescription
401Invalid credentials
400Invalid 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

GET /api/v1/collections/kpi/items

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

ParameterTypeDescription
limitintegerMaximum number of items (1-500)
tokenstringPagination 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

GET /api/v1/collections/kpi/items/{industry}

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

GET /api/v1/collections/kpi

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 /api/v1/stac

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

GET /api/v1/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 /api/v1/collections/sites

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

GET /api/v1/collections/sites/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

ParameterTypeRequiredDescription
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

GET /api/v1/collections/sites/items/{site_id}

Retrieve a single industrial site by its ID. Returns detailed information including emissions data, production metrics, and corporate ownership.

Path Parameters

ParameterTypeDescription
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

StatusDescription
404Site not found or not accessible by user
POST /api/v1/search

Search sites with spatial filters. Supports bounding box and GeoJSON geometry intersection queries.

i
The 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.

FieldTypeDescription
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

GET /api/v1/collections/sites/items/{site_id}/sai/items

List SAI observations for a site, sorted by date descending.

Query Parameters

ParameterTypeRequiredDescription
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

GET /api/v1/collections/sites/items/{site_id}/production

STAC Collection describing a site's production series (id production-{site_id}), with its spatial and temporal extent.

List Production Items

GET /api/v1/collections/sites/items/{site_id}/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

ParameterTypeRequiredDescription
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

PropertyTypeDescription
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

GET /api/v1/collections/sites/items/{site_id}/production/items/{item_id}

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

GET /api/v1/collections/sites/items/{site_id}/plume/items

List CO₂ plume inversion events for a site, sorted by acquisition date descending.

Query Parameters

ParameterTypeRequiredDescription
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

GET /api/v1/collections/sites/items/{site_id}/plume/items/{plume_id}/assets/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.

i
The output is reprojected to a north-up EPSG:4326 grid. Use ol-stac with displayGeoTiffByDefault: true to display it directly from the STAC item URL.

Path Parameters

ParameterTypeDescription
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

GET /api/v1/collections/panel/items

List all panels accessible to the authenticated user. Use ?industry=steel_bf to filter by industry.

Query Parameters

ParameterTypeRequiredDescription
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

GET /api/v1/collections/panel/items/{panel_id}/qai/items

List monthly QAI observations for a panel, sorted by date descending.

Query Parameters

ParameterTypeRequiredDescription
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

GET /api/v1/collections/panel/items/{panel_id}/production

STAC Collection describing a panel's production series (id panel-{panel_id}-production), with its spatial and temporal extent.

List Panel Production Items

GET /api/v1/collections/panel/items/{panel_id}/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

ParameterTypeRequiredDescription
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:

PropertyTypeDescription
production:site_count integer Number of sites with a nameplate capacity contributing to the value

Get a Panel Production Item

GET /api/v1/collections/panel/items/{panel_id}/production/items/{item_id}

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.

i
Requires activity access. Without it every alerts endpoint returns 403. Site items expose a related link titled Site Alerts only when at least one alert is visible to you.

List Alert Items

GET /api/v1/collections/alerts/items

List alert events across every site you can access, most recent event first.

Query Parameters

ParameterTypeRequiredDescription
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

GET /api/v1/collections/sites/items/{site_id}/alerts/items

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

ParameterTypeRequiredDescription
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.

i
Scoped to the sites you can access; no extra permission is needed. Site items expose a related link titled Site News only when at least one article exists.

List News Items

GET /api/v1/collections/news/items

List press articles across every site you can access, most recently published first.

Query Parameters

ParameterTypeRequiredDescription
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

GET /api/v1/collections/sites/items/{site_id}/news/items

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

ParameterTypeRequiredDescription
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

POST /api/v1/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

FieldTypeRequiredDescription
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.
i
Pass a month-start 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

StatusDescription
404No accessible site left among the ids sent — or the account has no activity access
422site_ids missing, or a date that is not ISO 8601

Export Panels

POST /api/v1/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

FieldTypeRequiredDescription
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.
i
Pass a month-start 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

StatusDescription
404No accessible panel left among the ids sent — or the account has no activity access
422panel_ids missing, or a date that is not ISO 8601