← Portal

API Documentation

KOSAVA v7 OpenData

Agencija za zaštitu životne sredine SEPA — API Documentation

The portal provides access to air quality and meteorological data collected through monitoring networks. The API is strictly read-only: there is no data submission, no authentication, and no request limits. All displayed data belongs to the organization indicated on the portal itself.

v1.2.0 Read-Only No Auth Required

Authentication

This API requires no authentication. All endpoints are publicly accessible. There are no API keys, tokens, or session cookies. Simply make HTTP GET requests to any endpoint listed below.

Auth Method

None

No credentials are needed for any endpoint. The portal is designed as an open data resource for public consumption.

Deployment Config

KEY_PREFIX & EXPECTED_SCHEMA_VERSION

These are server-side environment variables set by the operator at deploy time. They pin the portal to one organization's data namespace and enforce snapshot compatibility — they are not API parameters.

Base URL & Response Format

The base URL is deployment-specific. All paths in this documentation are relative to the root of wherever this portal is hosted. For a local development instance the base URL is typically http://localhost:8000.

Response Format

JSON (application/json)

All data endpoints return Content-Type: application/json. The export endpoint (/portal/export) returns a binary file download. HTML portal routes return text/html.

Measurement Windows

1h · 1d · 7d · 14d · 30d

The snapshot contains pre-aggregated data for five time windows. Not all windows may be present in every snapshot — check the supported_windows field on GET /meta.

Error Codes

All errors are returned as JSON with a field describing the problem. Health endpoints return fields instead.

200 OK

Success

The request succeeded. Response body contains the requested data.

400 Bad Request

Invalid Parameters

One or more required query parameters are missing, blank, not a valid integer (for IDs), or an unsupported value (for window or format).

404 Not Found

Resource Not Found

The organization, station, component, or requested measurement data does not exist in the active snapshot or has no public data available.

503 Service Unavailable

Snapshot Unavailable

Redis is unreachable, the active snapshot pointer is missing, or the snapshot schema version does not match EXPECTED_SCHEMA_VERSION. The portal is not ready to serve data.

Health

Lightweight probes for container orchestrators and load balancers. Neither endpoint requires authentication or touches measurement data.

GET /health/live Process liveness check

Confirms the process is alive and the HTTP server is accepting connections. This endpoint performs no I/O and always returns 200 OK as long as the process is running. Use as a container liveness probe.

Responses

StatusDescriptionBody Example
200 Process is alive {"status":"ok","check":"live"}

Code Examples

curl -s http://opendata.kosava.cloud/health/live
import requests

resp = requests.get("http://opendata.kosava.cloud/health/live")
print(resp.json())
# {"status": "ok", "check": "live"}
const resp = await fetch("http://opendata.kosava.cloud/health/live");
const data = await resp.json();
console.log(data);
// { status: "ok", check: "live" }
GET /health/ready Snapshot readiness check

Validates that the Redis connection is healthy and that the active snapshot schema version matches EXPECTED_SCHEMA_VERSION. Returns 200 with snapshot_id when ready. Returns 503 when Redis is unreachable, the snapshot pointer is missing, or the schema version is incompatible. Use as a container readiness probe and load-balancer health gate.

Responses

StatusDescriptionBody Example
200 Redis reachable, snapshot compatible {"status":"ok","check":"ready","snapshot_id":"snap_20240101_120000"}
503 Redis unavailable or schema mismatch {"status":"error","check":"ready","detail":"..."}

Code Examples

curl -s http://opendata.kosava.cloud/health/ready
import requests

resp = requests.get("http://opendata.kosava.cloud/health/ready")
if resp.status_code == 200:
    print("Ready:", resp.json()["snapshot_id"])
else:
    print("Not ready:", resp.json()["detail"])
const resp = await fetch("http://opendata.kosava.cloud/health/ready");
const data = await resp.json();
if (resp.ok) console.log("Snapshot:", data.snapshot_id);
else console.error("Not ready:", data.detail);

Metadata

Endpoints for discovering the structure of the dataset: organizations, monitoring networks, stations, and measurement components. Start here to learn which IDs to use in measurement queries. All metadata comes from the active Redis snapshot and is read-only.

GET /meta Snapshot manifest

Returns the manifest of the currently active Redis snapshot: generation timestamp, supported measurement windows, and aggregate counts for organizations, networks, stations, components, and measurement rows. Use supported_windows to know which window values are valid for measurement queries.

Responses

StatusDescription
200 Active snapshot manifest object
503 Snapshot unavailable or incompatible

Code Examples

curl -s http://opendata.kosava.cloud/meta | python3 -m json.tool
import requests

meta = requests.get("http://opendata.kosava.cloud/meta").json()
print("Snapshot:", meta["generated_at"])
print("Windows:", meta["supported_windows"])
print("Stations:", meta["counts"]["stations"])
const meta = await fetch("/meta").then(r => r.json());
console.log(meta.supported_windows); // ["1h","1d","7d","14d","30d"]
console.log(meta.counts.stations);   // 12
GET /organizations List all organizations

Returns all organizations in the active snapshot. Because one portal instance is pinned to a single organization via KEY_PREFIX, this list typically contains exactly one entry. Each object includes organization_id, name, and short_name.

Responses

StatusDescription
200Array of organization objects
503Snapshot unavailable

Code Examples

curl -s http://opendata.kosava.cloud/organizations
orgs = requests.get("http://opendata.kosava.cloud/organizations").json()
org_id = orgs[0]["organization_id"]  # typically 1 org
const orgs = await fetch("/organizations").then(r => r.json());
const orgId = orgs[0].organization_id;
GET /networks List all monitoring networks

Returns all monitoring networks defined in the active snapshot. Networks group stations under a named measurement programme or geographic area. Each object includes network_id, network_code, name, and short_name.

Responses

StatusDescription
200Array of network objects
503Snapshot unavailable

Code Examples

curl -s http://opendata.kosava.cloud/networks
networks = requests.get("http://opendata.kosava.cloud/networks").json()
for n in networks:
    print(n["network_code"], "-", n["name"])
const networks = await fetch("/networks").then(r => r.json());
networks.forEach(n => console.log(n.network_code, n.name));
GET /stations List all stations

Returns all public monitoring stations across all organizations and networks in the active snapshot. Each object includes station_id, station_name, station_code, organization_id, network_id, network_code, and geographic fields such as city and municipality where available. To filter by organization use GET /{organization_id}/stations.

Responses

StatusDescription
200Array of station objects
503Snapshot unavailable

Code Examples

curl -s http://opendata.kosava.cloud/stations
stations = requests.get("http://opendata.kosava.cloud/stations").json()
for s in stations:
    print(s["station_id"], s["station_name"], s["city"])
const stations = await fetch("/stations").then(r => r.json());
console.log(`${stations.length} stations found`);
GET /components List all measurement components

Returns all measurement components (pollutants and meteorological parameters) defined in the active snapshot. Each object includes component_id, short_name, and unit. Only publicly visible components appear here. Meteorological parameters include wind speed, temperature, humidity, and precipitation; pollutant parameters include particulate matter (PM10, PM2.5) and gaseous pollutants.

Responses

StatusDescription
200Array of component objects
503Snapshot unavailable

Code Examples

curl -s http://opendata.kosava.cloud/components
components = requests.get("http://opendata.kosava.cloud/components").json()
pm10 = next(c for c in components if c["short_name"] == "PM10")
print(pm10["component_id"], pm10["unit"])  # 1001 ug/m3
const components = await fetch("/components").then(r => r.json());
const pm10 = components.find(c => c.short_name === "PM10");
console.log(pm10.component_id, pm10.unit);
GET /{"{organization_id}"}/stations List stations for an organization

Returns all public monitoring stations for the specified organization. The response shape is identical to GET /stations but filtered to the given organization_id. Returns 404 if the organization does not exist or has no public stations in the active snapshot.

Path Parameters

NameTypeRequiredDescription
organization_id integer yes Organization ID. Obtain from GET /organizations.

Responses

StatusDescription
200Array of station objects for the organization
404Organization not found or no public stations
503Snapshot unavailable

Code Examples

curl -s http://opendata.kosava.cloud/1/stations
org_id = 1
stations = requests.get(
    f"http://opendata.kosava.cloud/{org_id}/stations"
).json()
print(f"{len(stations)} stations in org {org_id}")
const orgId = 1;
const stations = await fetch(`/${orgId}/stations`).then(r => r.json());
console.log(`${stations.length} stations`);
GET /{"{organization_id}"}/stations/{"{station_id}"}/meta Station metadata & public components

Returns detailed metadata for a single station, including the list of public measurement components (public_components) available for that station. Each component entry includes component_id, short_name, and unit. This is the starting point for discovering which components can be queried via the measurements endpoints. Returns 404 if the organization or station does not exist in the active snapshot.

Path Parameters

NameTypeRequiredDescription
organization_id integer yes Organization ID. Obtain from GET /organizations.
station_id integer yes Station ID. Obtain from GET /stations.

Responses

StatusDescription
200Station metadata object with embedded public_components array
404Organization or station not found
503Snapshot unavailable

Code Examples

curl -s http://opendata.kosava.cloud/1/stations/101/meta
meta = requests.get(
    "http://opendata.kosava.cloud/1/stations/101/meta"
).json()
for comp in meta["public_components"]:
    print(comp["component_id"], comp["short_name"], comp["unit"])
const meta = await fetch("/1/stations/101/meta").then(r => r.json());
meta.public_components.forEach(c =>
  console.log(c.component_id, c.short_name, c.unit)
);

Measurements

Endpoints for retrieving time-series environmental measurement data. All values are pre-computed in the active Redis snapshot — no real-time aggregation occurs. Use the metadata endpoints first to discover valid organization, station, and component IDs.

GET /measurements Time-series for a station component

Returns normalized time-series data for a single component at a single station over the requested time window. All four query parameters are required. The response includes station name, component details, the window used, and a points array sorted ascending by date and time. Each point has date (YYYY-MM-DD), time (HH:MM), and numeric value. The window parameter must be one of: 1h, 1d, 7d, 14d, 30d — and must be present in the snapshot's supported_windows.

Query Parameters

NameTypeRequiredDescriptionExample
organization_id integer yes Organization ID 1
station_id integer yes Station ID 101
component_id integer yes Component ID 1001
window string yes Time window. One of: 1h 1d 7d 14d 30d 1d

Responses

StatusDescription
200Normalized time-series payload with points array
400Missing, blank, or invalid parameter; unsupported window value
404Station or component not found, or no data for window
503Snapshot unavailable

Code Examples

curl -s \
  "http://opendata.kosava.cloud/measurements?organization_id=1&station_id=101&component_id=1001&window=1d"
import requests

resp = requests.get(
    "http://opendata.kosava.cloud/measurements",
    params={
        "organization_id": 1,
        "station_id": 101,
        "component_id": 1001,
        "window": "1d",
    },
)
resp.raise_for_status()
data = resp.json()
print(f"{data['station_name']} — {data['component_short_name']} ({data['unit']})")
for pt in data["points"]:
    print(pt["date"], pt["time"], pt["value"])
const params = new URLSearchParams({
  organization_id: "1",
  station_id: "101",
  component_id: "1001",
  window: "1d",
});
const resp = await fetch(`/measurements?${params}`);
if (!resp.ok) throw new Error(await resp.text());
const data = await resp.json();
console.log(`${data.station_name} — ${data.component_short_name} (${data.unit})`);
data.points.forEach(pt => console.log(pt.date, pt.time, pt.value));
GET /{"{organization_id}"}/stations/{"{station_id}"}/measurements/last_hour Last-hour measurements

Returns all component measurements recorded in the most recent hour for the specified station. The payload contains a hours array where each entry has a date (ISO-8601 date string), time (HH:MM), and a components list. Each component entry includes component_id and value (numeric). Returns 404 if the station does not exist or no last-hour data is available.

Path Parameters

NameTypeRequiredDescription
organization_id integer yes Organization ID
station_id integer yes Station ID

Responses

StatusDescription
200Last-hour payload with hours array, each containing a components list
404Station not found or no last-hour data
503Snapshot unavailable

Code Examples

curl -s http://opendata.kosava.cloud/1/stations/101/measurements/last_hour
data = requests.get(
    "http://opendata.kosava.cloud/1/stations/101/measurements/last_hour"
).json()
for hour in data["hours"]:
    print(hour["date"], hour["time"])
    for comp in hour["components"]:
        print("  component", comp["component_id"], "=", comp["value"])
const data = await fetch("/1/stations/101/measurements/last_hour")
  .then(r => r.json());
data.hours.forEach(hour => {
  console.log(hour.date, hour.time);
  hour.components.forEach(c => console.log(" component", c.component_id, "=", c.value));
});
GET /{"{organization_id}"}/stations/{"{station_id}"}/measurements/last_24h Last-24-hour measurements

Returns all component measurements recorded across the last 24 hours for the specified station. The payload structure is identical to last_hour: a hours array where each entry has date, time, and components. All components publicly exposed for the station are included in each hour entry where data exists. Returns 404 if the station does not exist or no last-24h data is available.

Path Parameters

NameTypeRequiredDescription
organization_id integer yes Organization ID
station_id integer yes Station ID

Responses

StatusDescription
200Last-24h payload with hours array across up to 24 hour entries
404Station not found or no last-24h data
503Snapshot unavailable

Code Examples

curl -s http://opendata.kosava.cloud/1/stations/101/measurements/last_24h
data = requests.get(
    "http://opendata.kosava.cloud/1/stations/101/measurements/last_24h"
).json()
print(f"{len(data['hours'])} hours of data")
const data = await fetch("/1/stations/101/measurements/last_24h")
  .then(r => r.json());
console.log(`${data.hours.length} hours of data`);

Export

Download measurement time-series as a structured file. Supported formats are CSV (UTF-8), XLSX (Excel), and ODS (LibreOffice Calc). The exported file includes full context columns — organization, network, station, component, date, time, unit, and numeric value.

GET /portal/export Download measurement data as a file

Builds and streams a downloadable spreadsheet file containing time-series measurement data for a single component at a single station. All five query parameters are required. The 1h window is not supported for export. The exported file contains one row per measurement point with columns: organization_id, organization_name, station_id, station_name, station_code, network_id, network_code, date, time, component_id, component_short_name, unit, value. Numeric values are formatted to two decimal places.

Query Parameters

NameTypeRequiredDescriptionExample
organization_id integer yes Organization ID 1
station_id integer yes Station ID 101
component_id integer yes Component ID 1001
window string yes Time window. One of: 1d 7d 14d 30d (1h not supported) 7d
format string yes File format. One of: csv xlsx ods csv

Responses

StatusContent-TypeDescription
200 text/csv / application/vnd.openxmlformats-officedocument.spreadsheetml.sheet / application/vnd.oasis.opendocument.spreadsheet File download with Content-Disposition: attachment
400application/jsonMissing/invalid parameter or unsupported window/format
404application/jsonStation or component not found, or no data for window
503application/jsonSnapshot unavailable

Code Examples

# Download as CSV
curl -OJ \
  "http://opendata.kosava.cloud/portal/export?organization_id=1&station_id=101&component_id=1001&window=7d&format=csv"

# Download as XLSX
curl -OJ \
  "http://opendata.kosava.cloud/portal/export?organization_id=1&station_id=101&component_id=1001&window=7d&format=xlsx"
import requests, re

resp = requests.get(
    "http://opendata.kosava.cloud/portal/export",
    params={
        "organization_id": 1,
        "station_id": 101,
        "component_id": 1001,
        "window": "7d",
        "format": "csv",
    },
)
resp.raise_for_status()

# Extract filename from Content-Disposition header
cd = resp.headers.get("Content-Disposition", "")
m = re.search(r'filename="(.+?)"', cd)
filename = m.group(1) if m else "export.csv"

with open(filename, "wb") as f:
    f.write(resp.content)
print(f"Saved: {filename}")
const params = new URLSearchParams({
  organization_id: "1", station_id: "101",
  component_id: "1001", window: "7d", format: "csv",
});
const resp = await fetch(`/portal/export?${params}`);
if (!resp.ok) throw new Error(await resp.text());

const blob = await resp.blob();
const cd = resp.headers.get("content-disposition") ?? "";
const filename = (cd.match(/filename="(.+?)"/) ?? [])[1] ?? "export.csv";

// Trigger browser download
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();

HVD API (/api/v1)

Public HVD-compliant API namespace conforming to Commission Implementing Regulation (EU) 2023/138 and the SEPA HVD profile. All routes return machine-readable JSON with standardized fields: data_status=preliminary, aggregation_type=hourly_mean, time_start_utc/time_end_utc in ISO 8601 UTC format. The original /meta, /stations, and /measurements contract remains unchanged for backward compatibility.

GET /api/v1/metadata HVD dataset descriptor

Returns the full HVD descriptor: schema_version, dataset_version, snapshot_id, retention_days, declared data_status (preliminary), aggregation_type (hourly_mean), supported_windows, applicable_legislation (ELI link to 2023/138), license, and links to legal pages (license, terms, qos, contact, dataset) and documentation (docs, redoc, openapi). Useful for harvesters and client integration checks.

Responses

StatusDescription
200Full HVD descriptor with license, retention policy, and links.
503Snapshot unavailable or incompatible

Code Examples

curl -s http://opendata.kosava.cloud/api/v1/metadata | python3 -m json.tool
import requests
m = requests.get("http://opendata.kosava.cloud/api/v1/metadata").json()
print(m["data_status"], m["retention_days"])
print(m["applicable_legislation"])
const meta = await fetch("/api/v1/metadata").then(r => r.json());
console.log(meta.license, meta.retention_days);
GET /api/v1/stations HVD stations

Returns all monitoring stations in HVD shape (Component A of the standard). Each station carries station_id (string), station_name, latitude/longitude, municipality, station_type (traffic/urban/background/industrial/rural/other), operator, active, last_updated_utc and optional fields (elevation_m, address, start_date, end_date, notes). Supported filters: active, municipality (case-insensitive), station_type, bbox (min_lon,min_lat,max_lon,max_lat).

Query Parameters

NameTypeRequiredDescription
activebooleannoFilter on active stations. true returns only active, false only inactive.
municipalitystringnoFilter by municipality name, case-insensitive exact match.
station_typestringnoFilter on HVD enum station type (traffic, urban, background, industrial, rural, other).
bboxstringnoBounding box "min_lon,min_lat,max_lon,max_lat" in WGS84. Returns stations inside the box.

Code Examples

curl -s "http://opendata.kosava.cloud/api/v1/stations?municipality=Kikinda"
stations = requests.get(
  "http://opendata.kosava.cloud/api/v1/stations",
  params={"bbox": "19,44,21,46"},
).json()
for s in stations:
  print(s["station_id"], s["station_name"], s["municipality"])
const stations = await fetch("/api/v1/stations?active=true").then(r => r.json());
stations.forEach(s => console.log(s.station_id, s.municipality));
GET /api/v1/parameters HVD parameter dictionary

Returns the controlled vocabulary of measurement parameters (Component B of the standard). Each entry has parameter_code (e.g. PM10, PM2.5, NO2, O3, SO2), parameter_name, unit and the fixed averaging_period=1h. Optional: cas_number, description.

Responses

StatusDescription
200Array of HVD parameter records.
503Snapshot unavailable or incompatible

Code Examples

curl -s http://opendata.kosava.cloud/api/v1/parameters
params = requests.get("http://opendata.kosava.cloud/api/v1/parameters").json()
codes = [p["parameter_code"] for p in params]
# ['SO2', 'PM10', 'O3', 'NO2', 'PM2.5', ...]
const params = await fetch("/api/v1/parameters").then(r => r.json());
GET /api/v1/observations HVD hourly preliminary observations

Returns hourly-averaged measurement records in HVD shape (Component C). Each record has station_id, parameter_code, time_start_utc, time_end_utc, value, unit, data_status=preliminary, aggregation_type=hourly_mean, published_at_utc, updated_at_utc, coverage. The window is automatically clipped to a rolling 30 days; the meta block reports requested_window, actual_window, and out_of_retention_window flag. Without filters returns all stations × all parameters (potentially large payload).

Query Parameters

NameTypeRequiredDescription
fromdatetimenoWindow start, ISO 8601 datetime (e.g. 2026-04-01T00:00:00Z). Missing TZ is treated as UTC. Default: generated_at - retention_days.
todatetimenoWindow end, ISO 8601 datetime. Missing TZ is treated as UTC. Default: generated_at.
station_idstringnoFilter on a single station. HVD station_id is the string version of the internal ID.
parameter_codestringnoFilter on a single parameter (e.g. PM10, PM2.5, NO2).

Code Examples

curl -s "http://opendata.kosava.cloud/api/v1/observations?station_id=1¶meter_code=PM10"
obs = requests.get(
  "http://opendata.kosava.cloud/api/v1/observations",
  params={
    "from": "2026-04-01T00:00:00Z",
    "to": "2026-04-08T00:00:00Z",
    "parameter_code": "PM2.5",
  },
).json()
print(obs["meta"]["actual_window"])
print(len(obs["data"]), "records")
const q = new URLSearchParams({station_id: "1", parameter_code: "PM10"});
const resp = await fetch(`/api/v1/observations?${q}`).then(r => r.json());
console.log(resp.meta.out_of_retention_window);
GET /api/v1/bulk HVD bulk download index

Returns the list of bulk resources for the active snapshot. Each entry has name, format, content_type, size_bytes, sha256, download_url. The bundle includes stations.csv/geojson, parameters.csv/json, observations CSV and Parquet (~13x smaller than CSV), README.md with data dictionary, and CHECKSUMS.sha256. Lazy on-demand build with snapshot-id keyed cache.

Responses

StatusDescription
200Bulk bundle index with SHA256 checksums.
503Snapshot unavailable or incompatible

Code Examples

curl -s http://opendata.kosava.cloud/api/v1/bulk | python3 -m json.tool
index = requests.get("http://opendata.kosava.cloud/api/v1/bulk").json()
for r in index["resources"]:
  print(r["name"], r["size_bytes"], r["sha256"])
const idx = await fetch("/api/v1/bulk").then(r => r.json());
console.log(idx.snapshot_id, idx.resources.length);
GET /api/v1/bulk/{filename} Download bulk resource

Streams a single bulk file as an attachment. Response headers include Content-Disposition (attachment; filename="..."), Content-Length, Content-Type, and X-Content-SHA256 (must match sha256 in the index).

Path Parameters

NameTypeRequiredDescription
filenamestringyesBulk resource filename from the index (e.g. observations_hourly_preliminary_last30days.parquet).

Responses

StatusDescription
200Bulk file as attachment with Content-Disposition and X-Content-SHA256 headers.
404Requested file does not exist in the current bulk bundle.
503Snapshot unavailable or incompatible

Code Examples

curl -O http://opendata.kosava.cloud/api/v1/bulk/observations_hourly_preliminary_last30days.parquet
import requests, pyarrow.parquet as pq
from io import BytesIO
data = requests.get("http://opendata.kosava.cloud/api/v1/bulk/observations_hourly_preliminary_last30days.parquet").content
table = pq.read_table(BytesIO(data))
print(table.num_rows, table.column_names)
const blob = await fetch("/api/v1/bulk/stations.geojson").then(r => r.blob());
// feed blob to Leaflet, OpenLayers, etc.
GET /api/v1/dcat DCAT-AP HVD JSON-LD catalog

Returns the DCAT-AP HVD catalog as application/ld+json with @context and a 16-node @graph: one dcat:Catalog, foaf:Organization (publisher) and vcard:Organization (contact), three dcat:Dataset (stations, parameters, observations), nine dcat:Distribution (API + CSV/GeoJSON/JSON/Parquet per dataset), one dcat:DataService. Every Dataset/Distribution carries dcatap:applicableLegislation (ELI 2023/138) and dcatap:hvdCategory (Earth observation and environment). Suitable for harvesting by EU/national portals.

Responses

StatusDescription
200DCAT-AP HVD catalog as application/ld+json with 16-node @graph.
503Snapshot unavailable or incompatible

Code Examples

curl -s -H "Accept: application/ld+json" http://opendata.kosava.cloud/api/v1/dcat
cat = requests.get("http://opendata.kosava.cloud/api/v1/dcat").json()
datasets = [n for n in cat["@graph"] if n.get("@type") == "dcat:Dataset"]
print(len(datasets), "datasets")
const cat = await fetch("/api/v1/dcat").then(r => r.json());
// JSON-LD compatible with rdflib, jsonld.js, Apache Jena

Field dictionary (HVD Section 11)

Structured dictionary of every field across the three dataset components. Definitions are available machine-readably at /api/v1/data-dictionary and here in three languages. Constant values and cross-component references are highlighted in the tables.

GET /api/v1/data-dictionary Structured field dictionary

Returns JSON with definitions of every field across the three components (stations, parameters, observations) plus the list of forbidden fields. Useful for client-side validation and auto-generated forms or documentation.

Query Parameters

NameTypeRequiredDescription
language string no Definition language. One of: sr-Latn, sr-Cyrl, en. Default: sr-Latn.

Forbidden fields (HVD Section 5.3.2): validated, qa_flag, compliance, exceedance

§5.1 Stations (HVD Component A)

Identification and geolocation of monitoring stations. /api/v1/stations returns the list with all fields below; bulk distributions are stations.csv and stations.geojson.

Name Type Required Constant Reference Description
station_id string yes Unique monitoring station identifier. Stable across time and snapshot generations; serves as primary key for the station in all distributions and as reference in observations.station_id.
station_name string yes Human-readable station name (e.g. "Kikinda Centar"). May contain local transliteration.
station_code string no Operator station code (e.g. "RS1002A") commonly used in reporting systems. Additional stable identifier; not part of the HVD required set but emitted for interoperability.
latitude decimal yes Station latitude in WGS84 coordinate system, decimal degrees.
longitude decimal yes Station longitude in WGS84 coordinate system, decimal degrees.
municipality string yes Name of the municipality in which the station is located. May be null when the data source does not have this information (field must exist in the schema).
station_type enum yes Station location type per HVD classification. Allowed values: traffic, urban, background, industrial, rural, other. May be null if the type is unknown.
operator string yes Name of the organization operating the station. Currently derived from organization.name at the portal instance level.
active boolean yes Whether the station is currently actively emitting measurements. true if the station is present in the active snapshot and operational; false if stopped or offline.
last_updated_utc datetime yes Timestamp of the last update of the station metadata in ISO 8601 UTC format. Currently derived from manifest.generated_at (snapshot-level timestamp).
elevation_m decimal no Station elevation above sea level in meters. Optional field, null if not available.
address string no Postal or street address of the station. Optional field, null if not available.
start_date date no Station operational start date in ISO 8601 format (YYYY-MM-DD). Optional field.
end_date date no Station operational end date in ISO 8601 format. null if the station is still operational.
notes string no Free-text notes about the station. Optional field.
network_id integer no Identifier of the monitoring network to which the station belongs. Not part of the HVD required set but emitted for cross-network navigation.
network_code string no Operator code of the monitoring network. Not part of the HVD required set.
§5.2 Parameters (HVD Component B)

Controlled vocabulary of measurement parameters. /api/v1/parameters returns an array; bulk distributions are parameters.csv and parameters.json.

Name Type Required Constant Reference Description
parameter_code string yes Parameter code (e.g. PM10, PM2.5, NO2, O3, SO2). Stable key referenced from observations.parameter_code.
parameter_name string yes Full human-readable parameter name (e.g. "Particulate matter <= 2.5 um"). Currently falls back to parameter_code until the snapshot emits component.name (v1.3.0).
unit string yes Unit of measurement in UCUM or similar notation (e.g. "ug.m-3" for micrograms per cubic meter). Consistent between parameters and observations distributions.
averaging_period string yes 1h Averaging period for values. Constant "1h" in v1.0 — all values are hourly averages.
cas_number string no CAS Registry Number for the pollutant (e.g. "10102-44-0" for NO2). Optional, null if not available.
description string no Free-text additional description of the parameter. Optional field.
§5.3 Hourly preliminary observations (HVD Component C)

Hourly-averaged measurements across all stations and parameters in a rolling 30-day window. /api/v1/observations returns JSON with data + meta block; bulk distributions are observations_hourly_preliminary_last30days.csv and .parquet.

Name Type Required Constant Reference Description
station_id string yes stations.station_id Monitoring station identifier. Reference to stations.station_id; must exist in the same snapshot.
parameter_code string yes parameters.parameter_code Parameter code. Reference to parameters.parameter_code; must exist in the same snapshot.
time_start_utc datetime yes Start of the hourly interval (inclusive) in ISO 8601 UTC with Z suffix. Record time range is [time_start_utc, time_end_utc).
time_end_utc datetime yes End of the hourly interval (exclusive) in ISO 8601 UTC. Always time_start_utc + 1h.
value decimal yes Hourly concentration of the parameter at the station, in the unit indicated by the unit field. null if measurement is unavailable for the interval.
unit string yes parameters.unit Unit of measurement for the value field. Consistent with parameters.unit for the same parameter_code.
data_status string yes preliminary Data validation status. Constant "preliminary" — data has not undergone formal QA/QC validation and is not intended for regulatory assessment.
aggregation_type string yes hourly_mean Value aggregation type. Constant "hourly_mean" — the value is a mean over the 60-minute interval [time_start_utc, time_end_utc).
published_at_utc datetime yes Timestamp of the value's first publication in ISO 8601 UTC. In v1.0 derived from manifest.generated_at (snapshot-level); per-record version arrives with snapshot v1.3.0.
updated_at_utc datetime yes Timestamp of the value's last modification in ISO 8601 UTC. Equal to published_at_utc if never modified. Currently snapshot-level; per-record version arrives with snapshot v1.3.0.
coverage decimal no Relative coverage of the hourly interval in [0, 1]. Value 1.0 means full coverage (60/60 minutes), 0.5 means half the interval. null if the system does not have this information (field must exist in the schema).
KOSAVA v7 OpenData — API v1.2.0 — Read-Only Public Data Portal — Swagger UIReDoc