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.
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.
Success
The request succeeded. Response body contains the requested data.
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).
Resource Not Found
The organization, station, component, or requested measurement data does not exist in the active snapshot or has no public data available.
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.
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
| Status | Description | Body 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" }
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
| Status | Description | Body 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.
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
| Status | Description |
|---|---|
| 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
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
| Status | Description |
|---|---|
| 200 | Array of organization objects |
| 503 | Snapshot 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;
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
| Status | Description |
|---|---|
| 200 | Array of network objects |
| 503 | Snapshot 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));
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
| Status | Description |
|---|---|
| 200 | Array of station objects |
| 503 | Snapshot 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`);
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
| Status | Description |
|---|---|
| 200 | Array of component objects |
| 503 | Snapshot 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);
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
| Name | Type | Required | Description |
|---|---|---|---|
organization_id |
integer | yes | Organization ID. Obtain from GET /organizations. |
Responses
| Status | Description |
|---|---|
| 200 | Array of station objects for the organization |
| 404 | Organization not found or no public stations |
| 503 | Snapshot 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`);
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
| Name | Type | Required | Description |
|---|---|---|---|
organization_id |
integer | yes | Organization ID. Obtain from GET /organizations. |
station_id |
integer | yes | Station ID. Obtain from GET /stations. |
Responses
| Status | Description |
|---|---|
| 200 | Station metadata object with embedded public_components array |
| 404 | Organization or station not found |
| 503 | Snapshot 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.
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
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
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
| Status | Description |
|---|---|
| 200 | Normalized time-series payload with points array |
| 400 | Missing, blank, or invalid parameter; unsupported window value |
| 404 | Station or component not found, or no data for window |
| 503 | Snapshot 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));
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
| Name | Type | Required | Description |
|---|---|---|---|
organization_id |
integer | yes | Organization ID |
station_id |
integer | yes | Station ID |
Responses
| Status | Description |
|---|---|
| 200 | Last-hour payload with hours array, each containing a components list |
| 404 | Station not found or no last-hour data |
| 503 | Snapshot 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)); });
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
| Name | Type | Required | Description |
|---|---|---|---|
organization_id |
integer | yes | Organization ID |
station_id |
integer | yes | Station ID |
Responses
| Status | Description |
|---|---|
| 200 | Last-24h payload with hours array across up to 24 hour entries |
| 404 | Station not found or no last-24h data |
| 503 | Snapshot 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.
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
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
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
| Status | Content-Type | Description |
|---|---|---|
| 200 | text/csv / application/vnd.openxmlformats-officedocument.spreadsheetml.sheet / application/vnd.oasis.opendocument.spreadsheet |
File download with Content-Disposition: attachment |
| 400 | application/json | Missing/invalid parameter or unsupported window/format |
| 404 | application/json | Station or component not found, or no data for window |
| 503 | application/json | Snapshot 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.
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
| Status | Description |
|---|---|
| 200 | Full HVD descriptor with license, retention policy, and links. |
| 503 | Snapshot 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);
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
| Name | Type | Required | Description |
|---|---|---|---|
active | boolean | no | Filter on active stations. true returns only active, false only inactive. |
municipality | string | no | Filter by municipality name, case-insensitive exact match. |
station_type | string | no | Filter on HVD enum station type (traffic, urban, background, industrial, rural, other). |
bbox | string | no | Bounding 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));
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
| Status | Description |
|---|---|
| 200 | Array of HVD parameter records. |
| 503 | Snapshot 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());
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
| Name | Type | Required | Description |
|---|---|---|---|
from | datetime | no | Window start, ISO 8601 datetime (e.g. 2026-04-01T00:00:00Z). Missing TZ is treated as UTC. Default: generated_at - retention_days. |
to | datetime | no | Window end, ISO 8601 datetime. Missing TZ is treated as UTC. Default: generated_at. |
station_id | string | no | Filter on a single station. HVD station_id is the string version of the internal ID. |
parameter_code | string | no | Filter 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);
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
| Status | Description |
|---|---|
| 200 | Bulk bundle index with SHA256 checksums. |
| 503 | Snapshot 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);
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
| Name | Type | Required | Description |
|---|---|---|---|
filename | string | yes | Bulk resource filename from the index (e.g. observations_hourly_preliminary_last30days.parquet). |
Responses
| Status | Description |
|---|---|
| 200 | Bulk file as attachment with Content-Disposition and X-Content-SHA256 headers. |
| 404 | Requested file does not exist in the current bulk bundle. |
| 503 | Snapshot 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.
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
| Status | Description |
|---|---|
| 200 | DCAT-AP HVD catalog as application/ld+json with 16-node @graph. |
| 503 | Snapshot 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.
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
| Name | Type | Required | Description |
|---|---|---|---|
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
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. |
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. |
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). |
Legal and operational pages
Legal frameworks and operational disclosures the portal publishes for HVD compliance. Every page is available in all three languages and both themes.
Dataset description
HVD-style dataset description per Section 9.2: title, summary, distributions (API + bulk + DCAT), legal basis.
License
Open data license under the Serbian E-Government Act (Article 26), functionally equivalent to CC BY: free reuse with attribution.
Terms of use
API terms of use: license, preliminary disclaimer, 30-day retention, regulatory disclaimer, contact.
QoS criteria
Target monthly availability 99%, no rate limits in v1.0, 24h advance maintenance notice, snapshot refreshed daily.
Contact
Contact information for questions about data, technical support, and legal aspects.