Quick Start

Get up and running in 3 steps:

1. Get your API key

Sign up to get your API key instantly. Then purchase credits or redeem a voucher to get started.

2. Make your first query

Query NO₂ air quality data for Paris, January 2023:

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.2&\
lon_min=2.0&lon_max=2.8&\
time_start=2023-01-01&time_end=2023-02-01&\
variables=no2" \
  -H "X-API-Key: sk_live_your_key_here"

3. Parse the response

json
{
  "status": "success",
  "query_time_ms": 18,
  "tiles_scanned": 2,
  "credits_used": 2,
  "credits_remaining": 198,
  "output": "Rows matched: 18200\nMin: 1.23\nMax: 42.5\nAverage: 9.87\n"
}

Add format=csv to get raw hourly data instead:

json
{
  "status": "success",
  "query_time_ms": 22,
  "tiles_scanned": 2,
  "credits_used": 2,
  "credits_remaining": 196,
  "format": "csv",
  "aggregate": "hourly",
  "output": "lat,lon,time,no2\n48.6500,2.1500,2023-01-01T00:00:00Z,12.34\n48.6500,2.1500,2023-01-01T01:00:00Z,11.87\n..."
}

Authentication

All API requests require an API key. You can pass it in two ways:

Header (recommended)

http
X-API-Key: sk_live_your_key_here

Query parameter

http
GET /api/v1/climate/query?api_key=sk_live_your_key_here&...

Security note: API keys are hashed with SHA-256 before storage. We never store your raw key.

Credits

Each query costs credits based on the data it touches:

Formula
credits = tiles × time_months × variables
FactorDescriptionExample
tiles Number of 5°×5° data tiles that cover your query area and exist on disk. Only tiles with actual data count — ocean / uncovered areas are free. Paris: 2 tiles, Netherlands: 2 tiles, Belgium: 4 tiles
time_months Number of calendar months in your time range Jan–Mar 2023: 3 months
variables Number of CAMS/ERA5 variables queried no2,t2m: 2 variables

Use dry_run=true to check the exact credit cost before executing a query — it returns the tile count and cost without deducting anything.

Tile boundaries and country size

Data is stored in 5°×5° latitude/longitude tiles. A country that straddles a 5° boundary uses more tiles than its geographic size alone would suggest. For example, Belgium (lat 49.5°–51.6°) crosses the 50° parallel, so it spans two tile rows (lat 45–50 and lat 50–55) × two tile columns = 4 tiles, while the Netherlands (lat 51°–53.7° mainland) fits within a single tile row = 2 tiles.

For named areas with overseas territories (e.g. France, Netherlands), the API automatically uses polygon-based filtering so you only pay for tiles that actually intersect the area's boundaries — not for empty ocean tiles within the bounding box.

Your remaining balance is returned in every response as credits_remaining. If you don't have enough credits, the API returns 402 Payment Required.

Query Endpoint

GET /api/v1/climate/query

Query climate data for a geographic region and time range.

Parameters

ParameterTypeRequiredDescription
lat_min float required* Minimum latitude (-90 to 90). *Not required when using lat/lon point shorthand.
lat_max float required* Maximum latitude (-90 to 90)
lon_min float required* Minimum longitude (-180 to 180)
lon_max float required* Maximum longitude (-180 to 180)
lat float optional Point query latitude. Use with lon instead of bbox params. Snaps to the nearest 0.1° grid point. See Point Query.
lon float optional Point query longitude. Use with lat.
time_start string required Start time. Accepts YYYY-MM-DD, YYYY-MM, YYYY-MM-DDTHH:MM:SSZ, or Unix timestamp (seconds)
time_end string required End time. Same formats as time_start. Both bounds are inclusive.
pollutants string optional Comma-separated list of variables to query. Default: no2.

CAMS (0.1°): no2, pm2p5, pm10, o3
ERA5 (0.25°): t2m, tp, blh, u10, v10
Derived ERA5: wind_speed, wind_dir — computed from u10+v10
ODIAC (0.01°): odiac_co2 — fossil-fuel CO₂ in gC/m²/month, global, 2020–2023
CAMS GHG Inversion (1°): ghg_co2 — CO₂ surface flux kgC/m²/month; ghg_ch4 — CH₄ flux kg/m²/s; ghg_n2o — N₂O flux kgN/m²/month. Global, monthly, 2016–present (~3 month lag). Aliases: atmospheric_co2, atmospheric_ch4, methane
MODIS (0.005°): modis_urban — urban fraction annual; modis_burned_area — monthly wildfire burned fraction, 2020–present

Mix freely in one request: variables=no2,t2m,blh. See ERA5 Variables.
format string optional stats (default) — summary statistics. csv — data rows. geojson — GeoJSON FeatureCollection. ndjson — one JSON object per line. See Output Formats.
aggregate string optional Controls time/space aggregation. Per-point: hourly (default), daily, monthly, annual, seasonal, trend, stddev, max, min, cumulative. Spatial averages: area_hourly, area_daily, area_monthly, diurnal. See Output Formats.
threshold float optional µg/m³ threshold for exceedance analysis. When set, returns hours-above-threshold per grid point instead of values. See Exceedance.
percentile float optional Percentile to compute (1–100) per grid point over the queried period. E.g. percentile=95 for the 95th percentile. See Percentile.
round integer optional Decimal places for output values: 06. Default is 4.
unit string optional ppb — convert gas concentrations from µg/m³ to parts-per-billion (no2 ×0.5226, o3 ×0.5013). Particulates and ERA5 variables are unaffected.
no_header boolean optional true — omit the CSV header row. Useful when appending to an existing file.
missing string optional null — emit rows with an empty value field for time/space cells that have no data, instead of skipping them.
dry_run boolean optional true — return credit cost and estimated row count without executing the query. Does not deduct credits.
area string optional Named area shortcut with polygon boundary masking. E.g. area=paris, area=belgium, area=france. Replaces explicit lat_min/lat_max/lon_min/lon_max and applies polygon filtering for CSV output — only grid cells inside the real boundary are returned. Accepts bbox= for backward compatibility. See Named Areas & Polygon Masking.
sort_by string optional Column name to sort CSV/ndjson output by. E.g. sort_by=no2_mean, sort_by=lat. Unknown column names are ignored. See Sort Output.
sort_dir string optional asc (default) or desc. Controls sort direction when sort_by is set.
compress string optional gzip — compress the response with gzip. Sets Content-Encoding: gzip. Most HTTP clients decompress automatically. See Gzip Compression.
facility string optional E-PRTR inspire_hash ID. Pins the query to the verified GPS coordinates of a registered EU industrial facility — replaces lat/lon or bbox. Use GET /api/v1/facilities to look up IDs. Enables return_divergence when combined with aggregate=trend.
return_divergence boolean optional Requires facility + aggregate=trend. When true, compares the satellite-derived CAMS NO₂ trend against the facility's E-PRTR self-reported NOₓ emissions trend. Returns a divergence object in the response with direction (consistent, divergent, or strongly_divergent), score, cams_no2_slope (µg/m³/yr), and eprtr_nox_slope (tonnes/yr). See Trend Analysis.

Response

json
{
  "status": "success",
  "query_time_ms": 18,
  "tiles_scanned": 2,
  "credits_used": 2,
  "credits_remaining": 6498,
  "format": "csv",        // present only for format=csv or threshold queries
  "aggregate": "hourly",  // present only for format=csv or threshold queries
  "output": "..."         // see Output Formats below
}

The output field contains the query result. Its format depends on the format and aggregate parameters — see Output Formats below.

Output Formats

The format and aggregate parameters together control the shape of the output field. All modes cost the same credits — only the queried tile count matters.

ModeParametersOutput rowsUse case
Statsformat=stats (default)4 scalar linesQuick min / max / avg summary
Hourlyformat=csv1 per grid point × hourRaw time series, full resolution
Dailyformat=csv&aggregate=daily1 per grid point × dayDay-by-day means
Monthlyformat=csv&aggregate=monthly1 per grid point × monthMonth-by-month means
Annualformat=csv&aggregate=annual1 per grid point × yearYear-over-year comparison
Seasonalformat=csv&aggregate=seasonal4 per grid point (DJF/MAM/JJA/SON)Climatological season means
Trendformat=csv&aggregate=trend1 per grid pointOLS slope (value/year), R², n months
Std Devformat=csv&aggregate=stddev1 per grid pointTemporal mean and sample standard deviation
Area hourlyaggregate=area_hourly1 per hourRegional mean time series
Area dailyaggregate=area_daily1 per dayRegional daily trend
Area monthlyaggregate=area_monthly1 per monthRegional monthly trend
Diurnalaggregate=diurnal24 (one per hour-of-day)Typical daily cycle
Exceedancethreshold=N1 per grid pointHours above a µg/m³ limit
Percentilepercentile=N1 per grid pointP50, P95, P98 … per location
GeoJSONformat=geojsonsame rows as CSVMap-ready output for Mapbox / Leaflet / QGIS
NDJSONformat=ndjsonsame rows as CSVOne JSON object per line — streams into Python/pandas pipelines
Maxformat=csv&aggregate=max1 per grid pointTemporal peak value (WHO compliance, extreme events)
Minformat=csv&aggregate=min1 per grid pointTemporal minimum value
Cumulativeformat=csv&aggregate=cumulative1 per grid point × dayRunning cumulative sum — ideal for precipitation totals (tp)

format=stats (default)

Returns aggregate statistics over the entire query region and time range. Useful for quick summaries.

text
Rows matched: 18200
Min: 1.230000
Max: 42.500000
Average: 9.870000

format=csv & aggregate=hourly (default for csv)

Returns one row per grid point per hour. Time is ISO 8601 UTC.

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01-01&time_end=2023-01-02&\
variables=no2&format=csv&aggregate=hourly" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,time,no2
48.5500,2.0500,2023-01-01T00:00:00Z,8.32
48.5500,2.0500,2023-01-01T01:00:00Z,7.14
48.5500,2.1500,2023-01-01T00:00:00Z,9.01
...

format=csv & aggregate=daily

Returns the daily mean per grid point. 24× fewer rows than hourly.

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01-01&time_end=2023-01-31&\
variables=no2&format=csv&aggregate=daily" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,date,no2_mean
48.5500,2.0500,2023-01-01,8.93
48.5500,2.0500,2023-01-02,7.65
48.5500,2.1500,2023-01-01,9.12
...

format=csv & aggregate=monthly

Returns the monthly mean per grid point. Great for trend analysis across months.

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01&time_end=2023-12&\
variables=no2&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,year_month,no2_mean
48.5500,2.0500,2023-01,9.87
48.5500,2.0500,2023-02,8.34
48.5500,2.1500,2023-01,10.23
...

Row limit

Queries that would return more than 500,000 rows are rejected with 400 Bad Request and a message suggesting to use aggregate=daily or aggregate=monthly, or to reduce the time range.

Point Query

Use lat and lon instead of the four bbox params to query a single grid point. The coordinates are automatically snapped to the nearest 0.1° grid point.

bash
# Hourly NO₂ at the Eiffel Tower, January 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat=48.8566&lon=2.3522&\
time_start=2023-01-01&time_end=2023-01-31&\
variables=no2&format=csv" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,time,no2
48.8500,2.3500,2023-01-01T00:00:00Z,12.34
48.8500,2.3500,2023-01-01T01:00:00Z,11.87
...

Grid snapping: The data grid has cell centres at …, −0.05°, 0.05°, 0.15°, 0.25°, … The API snaps your coordinates to the nearest cell centre so you always get exactly one point back, regardless of how precisely you specify the coordinates.

Exceedance Analysis

Set threshold to a µg/m³ value to get a per-grid-point count of hours above that threshold over the queried period. Useful for air quality index calculations and health exposure studies.

bash
# Hours of NO₂ above 25 µg/m³ in Paris, January 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01-01&time_end=2023-01-31&\
variables=no2&threshold=25" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,hours_above,total_hours,pct_above
48.5500,2.0500,22,744,2.96
48.5500,2.1500,31,744,4.17
48.6500,2.0500,18,744,2.42
...
ColumnDescription
hours_aboveNumber of hours the pollutant exceeded the threshold
total_hoursTotal hours in the queried period for this grid point
pct_abovePercentage of hours above the threshold (0–100)

Area Mean (Spatial Average)

Use aggregate=area_hourly, area_daily, or area_monthly to get a single spatially-averaged time series for the entire queried bounding box. Instead of one row per grid point, you get one row per time step — ideal for regional trend analysis.

bash
# Daily spatial average for Paris bounding box, January 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01-01&time_end=2023-01-31&\
variables=no2&format=csv&aggregate=area_daily" \
  -H "X-API-Key: sk_live_your_key_here"
csv
date,no2_mean
2023-01-01,9.41
2023-01-02,8.73
2023-01-03,11.20
...
aggregate valueTime columnRows per pollutant
area_hourlytime (ISO 8601)1 per hour
area_dailydate (YYYY-MM-DD)1 per day
area_monthlyyear_month (YYYY-MM)1 per month

Annual Aggregate

Use aggregate=annual with format=csv to get the annual mean per grid point. Useful for comparing year-over-year trends across a region.

bash
# Annual mean per grid point, 2023 full year
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01-01&time_end=2023-12-31&\
variables=no2&format=csv&aggregate=annual" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,year,no2_mean
48.5500,2.0500,2023,8.72
48.5500,2.1500,2023,9.10
48.6500,2.0500,2023,7.95
...

Seasonal Aggregate

Use aggregate=seasonal with format=csv to get the climatological mean per grid point for each of the four meteorological seasons: DJF (Dec–Feb), MAM (Mar–May), JJA (Jun–Aug), SON (Sep–Nov). Seasons are computed from the full period in the query range.

bash
# Seasonal NO₂ means for Paris, 2020–2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2020-01-01&time_end=2023-12-31&\
variables=no2&format=csv&aggregate=seasonal" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,season,no2_mean
48.5500,2.0500,DJF,14.32
48.5500,2.0500,MAM,10.87
48.5500,2.0500,JJA,7.54
48.5500,2.0500,SON,12.16
48.6500,2.0500,DJF,13.91
...

Trend Analysis

Use aggregate=trend with format=csv to compute a per-grid-point linear regression over the monthly means in your query range. Returns the OLS slope (value change per year), intercept, , and n (number of months with data). Only cells with at least 3 data months are emitted.

bash
# NO₂ long-term trend for Paris, 2015–2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2015-01-01&time_end=2023-12-31&\
variables=no2&format=csv&aggregate=trend" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,slope,intercept,r2,n
48.5500,2.0500,-0.523847,1061.234567,0.8812,108
48.5500,2.1500,-0.491203,995.672341,0.8534,108
48.6500,2.0500,-0.507621,1028.451892,0.8701,108
...

The slope is in µg/m³/year (or ppb/year when unit=ppb). A negative slope indicates improving air quality over the period.

Facility-pinned trend & divergence analysis

Add facility=<inspire_hash> to anchor a trend query to the verified GPS coordinates of a registered E-PRTR industrial facility. Add return_divergence=true to cross-validate the satellite-derived trend against the facility's annual self-reported NOₓ emissions from the European Environment Agency's E-PRTR registry.

bash
# BASF Ludwigshafen — 10-year NO₂ trend + divergence vs E-PRTR NOₓ
curl "https://api.jiskta.com/api/v1/climate/query?\
facility=12626246128710077482&\
time_start=2013-01-01&time_end=2023-12-31&\
variables=no2&format=csv&aggregate=trend&\
return_divergence=true" \
  -H "X-API-Key: sk_live_your_key_here"
json
{
  "status": "success",
  "query_time_ms": 34,
  "tiles_scanned": 1,
  "credits_used": 1,
  "credits_remaining": 9999,
  "output": "lat,lon,slope,intercept,r2,n\n49.5150,8.4250,-0.312,24.891,0.763,132",
  "divergence": {
    "direction": "consistent",
    "score": 0.12,
    "cams_no2_slope": -0.312,
    "eprtr_nox_slope": -4820.5
  }
}
Divergence fieldTypeDescription
directionstringconsistent — satellite and E-PRTR trends agree. divergent — significant disagreement. strongly_divergent — opposing trends (satellite improves, self-reported worsens, or vice versa).
scorefloatNormalised divergence magnitude (0 = perfect agreement). Higher means more disagreement.
cams_no2_slopefloatOLS slope from satellite CAMS NO₂ data (µg/m³/year). Negative = improving.
eprtr_nox_slopefloatOLS slope from E-PRTR self-reported NOₓ emissions (tonnes/year). Negative = improving. Only NOₓ is currently cross-validated.

Use GET /api/v1/facilities to search for facility inspire_hash IDs by name, country, or GPS. The Query Builder Facilities tab also supports interactive search with a one-click Use in Climate Query action.

Standard Deviation

Use aggregate=stddev with format=csv to compute the temporal mean and sample standard deviation per grid point over the query range. Returns one row per grid point. Only cells with at least 2 valid data points are emitted.

bash
# NO₂ mean and variability for Paris, 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat=48.85&lon=2.35&\
time_start=2023-01&time_end=2023-12&\
variables=no2&format=csv&aggregate=stddev" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,no2_mean,no2_stddev
48.8500,2.3500,16.4821,8.2134

The stddev column is the sample standard deviation (divided by n−1). High stddev indicates high temporal variability — useful for identifying locations with episodic pollution events vs. constant baseline exposure.

Diurnal Profile

Use aggregate=diurnal to get the average value per hour-of-day (0–23), spatially averaged over the bounding box. Shows the typical daily cycle of a pollutant over your chosen period.

bash
# Hourly-of-day NO₂ profile for Paris, January 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01-01&time_end=2023-01-31&\
variables=no2&format=csv&aggregate=diurnal" \
  -H "X-API-Key: sk_live_your_key_here"
csv
hour,no2_mean
0,12.79
1,11.43
...
12,14.20
...
23,13.55

Percentile Analysis

Add percentile=N (1–100) to compute the Nth percentile of all hourly values per grid point over the queried period. Commonly used for regulatory compliance (e.g., the 98th percentile for NO₂ limit value assessments).

bash
# 95th percentile NO₂ per grid point, January 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01-01&time_end=2023-01-31&\
variables=no2&percentile=95" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,p95
48.5500,2.0500,23.82
48.5500,2.1500,21.47
48.6500,2.0500,19.63
...

The aggregate field in the response is set to percentile_N (e.g. percentile_95).

GeoJSON Output

Set format=geojson to receive a GeoJSON FeatureCollection instead of CSV. Each feature is a Point with coordinates [lon, lat] and all data values in properties. Compatible with Mapbox, Leaflet, QGIS, and any GeoJSON-aware tool.

bash
# Monthly mean in GeoJSON format
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01&time_end=2023-01&\
variables=no2&format=geojson&aggregate=monthly" \
  -H "X-API-Key: sk_live_your_key_here"
json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": { "type": "Point", "coordinates": [2.05, 48.55] },
      "properties": { "year_month": "2023-01", "no2_mean": 9.87 }
    },
    ...
  ]
}

Note: GeoJSON output is supported for all per-point modes (hourly, daily, monthly, annual, exceedance, percentile). Area and diurnal aggregates have no spatial dimension and are returned as CSV regardless of the format parameter.

NDJSON Output

Set format=ndjson to receive Newline-Delimited JSON — one JSON object per line. Compatible with all aggregate modes. Each line is a complete, self-describing JSON object. Ideal for streaming into Python/pandas without loading a full CSV into memory, and for REST pipelines that prefer JSON.

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat=48.85&lon=2.35&\
time_start=2023-01&time_end=2023-03&\
variables=no2&format=ndjson&aggregate=monthly" \
  -H "X-API-Key: sk_live_..."
text
{"lat":48.8500,"lon":2.3500,"year_month":"2023-01","no2_mean":25.67}
{"lat":48.8500,"lon":2.3500,"year_month":"2023-02","no2_mean":36.93}
{"lat":48.8500,"lon":2.3500,"year_month":"2023-03","no2_mean":23.37}

Numeric values are emitted as JSON numbers (no quotes). Timestamp/date columns are strings. Empty cells become null.

Max / Min Aggregate

Use aggregate=max or aggregate=min with format=csv to get the temporal peak or minimum value per grid point over the queried period. Returns one row per grid point — useful for regulatory compliance (WHO daily limits), extreme event analysis, and ranking locations by worst-case pollution.

bash
# Peak NO₂ per location in Paris, full year 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
area=paris&time_start=2023-01&time_end=2023-12&\
variables=no2&format=csv&aggregate=max&sort_by=no2_max&sort_dir=desc" \
  -H "X-API-Key: sk_live_..."
text
lat,lon,no2_max
48.8500,2.3500,87.4321
48.8500,2.4500,84.1209
48.7500,2.3500,79.8834

Column name: {var}_max for max, {var}_min for min. Works with all variables and multi-variable queries.

Cumulative Sum

Use aggregate=cumulative with format=csv to get a running cumulative sum per grid point. Returns one row per grid point × day, with the value increasing over time. Primarily designed for tp (total precipitation) to compute seasonal or annual totals.

bash
# Cumulative precipitation in Paris, Jan 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat=48.85&lon=2.35&\
time_start=2023-01-01&time_end=2023-01-31&\
variables=tp&format=csv&aggregate=cumulative" \
  -H "X-API-Key: sk_live_..."
text
lat,lon,date,tp_cumsum
48.8500,2.3500,2023-01-01,0.0012
48.8500,2.3500,2023-01-02,0.0031
48.8500,2.3500,2023-01-03,0.0055
...

Named Areas & Polygon Masking

Pass area=name instead of explicit lat_min/lat_max/lon_min/lon_max to use a predefined area. Named areas apply polygon boundary masking for CSV output — grid cells outside the real geographic boundary are filtered out.

The area index is powered by OpenStreetMap administrative boundaries at all admin levels (2=country, 4=province/region, 6=department, 8=city/municipality). Use GET /api/v1/areas?q=name to discover area IDs and bboxes. You can also look up by OSM relation ID: area=osm:71525.

bash
# Discover areas matching a name
curl "https://api.jiskta.com/api/v1/areas?q=paris"

# Look up Brussels by OSM relation ID
curl "https://api.jiskta.com/api/v1/areas?osm=54094"

# Find areas overlapping a bounding box
curl "https://api.jiskta.com/api/v1/areas?bbox=48,49,2,3"
json
{
  "status": "ok",
  "source": "libarea",
  "count": 1,
  "areas": [
    { "id": 123, "name": "Paris", "admin_level": 8, "parent": "Île-de-France",
      "osm_id": 71525, "lat_min": 48.81, "lat_max": 48.90,
      "lon_min": 2.22, "lon_max": 2.47 }
  ]
}
bash
# Query by name (polygon-masked, only cells inside Paris city boundary)
curl "https://api.jiskta.com/api/v1/climate/query?\
area=paris&time_start=2023-01&time_end=2023-12&\
variables=no2&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_..."

# Query by OSM relation ID (exact polygon boundary)
curl "https://api.jiskta.com/api/v1/climate/query?\
area=osm:54094&time_start=2023-01&time_end=2023-12&\
variables=no2&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_..."

# Belgium - only cells inside Belgian polygon (not rectangle)
curl "https://api.jiskta.com/api/v1/climate/query?\
area=belgium&time_start=2023-01&time_end=2023-01&\
variables=no2&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_..."

Response includes:

json
{
  "status": "success",
  "area": "belgium",
  "area_polygon": {
    "type": "MultiPolygon",
    "coordinates": [[ [[3.31,49.51],[2.52,49.51],... ] ]]
  },
  "output": "lat,lon,year_month,no2_mean\n..."
}

Name normalization: Names are case-insensitive and treat spaces, underscores, and hyphens identically. area=new-york, area=New York, and area=new_york are all equivalent.

Note: Polygon masking applies to format=csv output. For format=stats, statistics cover all grid points within the queried tiles. Credits are based on tiles that intersect the area polygon — overseas territories are included if they have data tiles, but empty ocean tiles within the bounding box are excluded.

The bbox= parameter is still accepted as a backward-compatible alias for area=.

Data source: OpenStreetMap administrative boundaries (© ODbL contributors) via a pre-built io_uring binary index (areas_v2.bin). Covers all European admin levels 2–8 from the Europe OSM extract.

NameTypeSource
parisCity (level 8)OSM commune boundary
londonCity (level 8)OSM Greater London boundary
berlinCity (level 4)OSM city-state boundary
amsterdamCity (level 8)OSM municipality boundary
brusselsCity (level 8)OSM municipality boundary
franceCountry (level 2)OSM national boundary
germanyCountry (level 2)OSM national boundary
belgiumCountry (level 2)OSM national boundary
osm:<relation_id>Any areaDirect OSM relation ID lookup
All OSM administrative relations at admin_level 2, 4, 6, or 8 for Europe are indexed. Use /api/v1/areas?q=name to discover any area.

Area Discovery API

Use GET /api/v1/areas to discover named areas and their bounding boxes. No authentication required.

ParamTypeDescription
qstringName search (case-insensitive, underscore/hyphen/space equivalent). Returns first match.
bboxstringComma-separated lat_min,lat_max,lon_min,lon_max. Returns up to 50 areas overlapping this rectangle.
osmintegerOSM relation ID. Returns the matching area.
idintegerDirect internal area_id lookup.
bash
# Search by name
curl "https://api.jiskta.com/api/v1/areas?q=amsterdam"

# All areas overlapping Paris bbox
curl "https://api.jiskta.com/api/v1/areas?bbox=48.8,48.9,2.2,2.5"

# Look up by OSM relation ID
curl "https://api.jiskta.com/api/v1/areas?osm=54094"
json
{
  "status": "ok",
  "source": "libarea",
  "count": 3,
  "areas": [
    { "id": 1, "name": "Amsterdam", "admin_level": 8, "parent": "North Holland",
      "osm_id": 271110, "lat_min": 52.27, "lat_max": 52.43,
      "lon_min": 4.73, "lon_max": 5.08 },
    { "id": 2, "name": "North Holland", "admin_level": 4, "parent": "Netherlands",
      "osm_id": 47772, "lat_min": 52.27, "lat_max": 53.17,
      "lon_min": 4.46, "lon_max": 5.31 }
  ]
}

Pass the returned id to the area= parameter via area=osm:<osm_id>, or use the area name directly.

POST with GeoJSON Mask

For custom polygon boundaries (industrial zones, watersheds, administrative districts), use the POST /api/v1/climate/query endpoint with a JSON body. Include a mask field containing a GeoJSON Polygon or MultiPolygon geometry. All other query parameters can be passed in the JSON body or as URL query parameters.

bash
# Query only within a custom polygon (GeoJSON lon,lat order)
curl -X POST "https://api.jiskta.com/api/v1/climate/query" \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "lat_min": 48.7, "lat_max": 49.0,
    "lon_min": 2.2,  "lon_max": 2.5,
    "time_start": "2023-01", "time_end": "2023-01",
    "variables": "no2", "format": "csv", "aggregate": "monthly",
    "mask": {
      "type": "Polygon",
      "coordinates": [[[2.30,48.80],[2.45,48.80],[2.45,48.95],[2.30,48.95],[2.30,48.80]]]
    }
  }'
python
import requests, json

mask = {
    "type": "Polygon",
    "coordinates": [[[2.30,48.80],[2.45,48.80],[2.45,48.95],[2.30,48.95],[2.30,48.80]]]
}

resp = requests.post(
    "https://api.jiskta.com/api/v1/climate/query",
    headers={"X-API-Key": "sk_live_..."},
    json={
        "lat_min": 48.7, "lat_max": 49.0,
        "lon_min": 2.2,  "lon_max": 2.5,
        "time_start": "2023-01", "time_end": "2023-01",
        "variables": "no2", "format": "csv", "aggregate": "monthly",
        "mask": mask,
    }
)
print(resp.json()["output"])

GeoJSON coordinate order: GeoJSON uses [longitude, latitude] order (opposite of most climate conventions). Coordinates outside the query bbox are silently ignored — only points within both the bbox and the polygon are returned.

Supported geometry types: Polygon and MultiPolygon. The outer ring of each polygon is used for masking; holes (inner rings) are not supported in this version.

Credits: Charged by bounding box tiles, not polygon area. A polygon that covers 10% of a tile still charges for the full tile.

Sort Output

Add sort_by=column&sort_dir=asc|desc to sort CSV or ndjson output by any column. Sort is applied after all data is computed. Uses a stable sort, so rows with equal values preserve their original order.

bash
# Top 10 most polluted cells in Paris, January 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
area=paris&time_start=2023-01&time_end=2023-01&\
variables=no2&format=csv&aggregate=monthly&\
sort_by=no2_mean&sort_dir=desc" \
  -H "X-API-Key: sk_live_..."

If sort_by refers to a column that doesn't exist in the output (e.g. wrong variable name), the output is returned unsorted — no error is raised.

Gzip Compression

Add compress=gzip to receive a gzip-compressed response. The server sets Content-Encoding: gzip. Most HTTP clients (curl, Python requests, fetch) decompress automatically. Large daily/hourly CSV outputs typically compress 5–10× (e.g. 2 MB → 200 KB).

bash
# curl decompresses automatically with --compressed
curl --compressed "https://api.jiskta.com/api/v1/climate/query?\
area=france&time_start=2023-01&time_end=2023-12&\
variables=no2&format=csv&aggregate=daily&compress=gzip" \
  -H "X-API-Key: sk_live_..."

Conditional GET (304 Not Modified)

Every query response includes an ETag header (a hash of the query parameters). On repeat requests, send the ETag back in an If-None-Match header — if the data hasn't changed, the server responds with 304 Not Modified and an empty body, saving bandwidth and credits.

bash
# First request — save the ETag
ETAG=$(curl -sI "https://api.jiskta.com/api/v1/climate/query?\
lat=48.85&lon=2.35&time_start=2023-01&time_end=2023-01&variables=no2&format=stats" \
  -H "X-API-Key: sk_live_..." | grep -i etag | awk '{print $2}')

# Repeat request — 304 if data unchanged (deducts 0 credits)
curl -si "https://api.jiskta.com/api/v1/climate/query?\
lat=48.85&lon=2.35&time_start=2023-01&time_end=2023-01&variables=no2&format=stats" \
  -H "X-API-Key: sk_live_..." \
  -H "If-None-Match: $ETAG"
# → HTTP/2 304 (empty body, 0 credits used)

Historical data (validated reanalysis, 2013–2023) is immutable — ETags are stable indefinitely. For interim reanalysis periods (recent months), data may be upgraded to validated reanalysis during monthly sync; in that case the ETag will change.

CAMS Air Quality Variables

Copernicus Atmosphere Monitoring Service (CAMS) reanalysis. 0.1° spatial resolution, hourly values, European coverage, 2013–2025.

VariableParameter valueUnitDescription
Nitrogen Dioxideno2µg/m³Combustion byproduct. Key indicator for urban traffic and industrial pollution.
Fine Particulate Matterpm2p5µg/m³Particles <2.5µm. Core metric for public health and air quality indices.
Coarse Particulate Matterpm10µg/m³Particles <10µm. Regulatory standard for dust and industrial emissions.
Ozoneo3µg/m³Surface ozone. Critical for photochemical smog and vegetation impact.

ERA5 Meteorological Variables

ECMWF ERA5 global atmospheric reanalysis, European domain (30°N–70°N, 25°W–40°E). 0.25° spatial resolution, hourly values, 2013–2026.

VariableParameter valueUnitDescription
2m Temperaturet2mKAir temperature at 2 metres. Correlates with O₃ formation; detect thermal inversions.
Total Precipitationtpm/hTotal precipitation. Model wet deposition and pollutant wash-out events.
Boundary Layer HeightblhmAtmospheric boundary layer height. Controls vertical dispersion of pollutants.
U Wind Component (10 m)u10m/sEastward wind at 10 m. Analyse pollutant transport direction.
V Wind Component (10 m)v10m/sNorthward wind at 10 m. Combine with u10 for full wind vectors.
Wind Speed (derived)wind_speedm/sHorizontal wind speed = √(u10² + v10²). Computed on-the-fly from ERA5 tiles — no extra credits vs requesting u10+v10 separately.
Wind Direction (derived)wind_dirdegreesMeteorological FROM direction: 0°=N, 90°=E, 180°=S, 270°=W. Computed from u10/v10; not available with aggregate=trend.

Wind speed and direction example

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat=48.85&lon=2.35&\
time_start=2023-01-01&time_end=2023-12-31&\
variables=wind_speed,wind_dir,no2&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,year_month,wind_speed,wind_dir,no2_mean
48.8500,2.3500,2023-01,3.1209,245.8,13.2847
48.8500,2.3500,2023-02,2.8941,212.4,11.9302
...
bash
curl "https://api.jiskta.com/api/v1/climate/query?\n lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\n time_start=2023-01-01&time_end=2023-01-31&\n variables=t2m,blh&format=csv&aggregate=daily" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,date,t2m_mean,blh_mean
48.5500,2.0500,2023-01-01,275.4,342.1
48.5500,2.0500,2023-01-02,274.8,289.7
...

Tip: Query CAMS, ERA5, and VIIRS variables together — variables=no2,t2m,viirs — credits are charged as tiles × months × variables.

VIIRS Nighttime Lights

NASA Black Marble VNP46A3 monthly composite radiance. 0.005° spatial resolution (~500 m), monthly values, 2020–present, global coverage.

VariableParameter valueUnitDescription
Nighttime RadianceviirsnW/cm²/srMonthly cloud-free composite. Snow-free composite (AllAngle_Composite_Snow_Free). Higher values = more artificial light. 0 = ocean/no-data.
  • Resolution: 0.005° (~500 m) — 20× finer than CAMS
  • Coverage: global land areas, 2020-01 to present
  • Format: same as CAMS/ERA5 — use format=csv, aggregate=monthly, or format=stats
  • Point queries (lat=/lon=) snap to nearest 0.005° cell

Example: city radiance time series

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat=48.85&lon=2.35&\
time_start=2020-01&time_end=2024-12&\
variables=viirs&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_your_key_here"
csv
lat,lon,year_month,viirs_mean
48.8500,2.3500,2020-01,64.3
48.8500,2.3500,2020-02,61.8
...

Example: combine with air quality

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.5&lat_max=49.0&lon_min=2.0&lon_max=2.5&\
time_start=2023-01&time_end=2023-12&\
variables=viirs,no2&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_your_key_here"

GHSL Human Settlement

JRC Global Human Settlement Layer (GHSL) — population density and built-up surface area at 0.005° spatial resolution (~500 m). Epoch-based (not monthly): 2010, 2015, 2020, 2025. Global coverage. Queries for any date are automatically snapped to the nearest epoch.

VariableParameter valueUnitDescription
Population densityghsl_poppersons/cellGHS-POP: resident population count per 0.005° cell (R2023A release). Epochs: 2010, 2015, 2020, 2025.
Built-up surface (total)ghsl_builtGHS-BUILT-S: total built-up surface area per cell (residential + non-residential).
Built-up surface (non-res)ghsl_built_nresGHS-BUILT-S-NRES: non-residential built-up surface area per cell (offices, industry, infrastructure).
  • Resolution: 0.005° (~500 m) — same grid as VIIRS
  • Coverage: global, epochs 2010 / 2015 / 2020 / 2025
  • Epoch snapping: a query for time_start=2023-01 returns the 2020 epoch (nearest floor). A query for time_start=2024-01 returns the 2025 epoch (nearest).
  • Format: use format=stats or format=csv&aggregate=monthly. Since GHSL is epoch-based, all monthly rows within an epoch return the same value.

Example: population density in Paris region

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.7&lat_max=49.0&lon_min=2.2&lon_max=2.5&\
time_start=2020-01&time_end=2020-01&\
variables=ghsl_pop&format=stats" \
  -H "X-API-Key: sk_live_your_key_here"

Example: built-up surface trend (2010 vs 2020)

bash
# 2010 epoch
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.7&lat_max=49.0&lon_min=2.2&lon_max=2.5&\
time_start=2010-01&time_end=2010-01&\
variables=ghsl_built&format=stats" \
  -H "X-API-Key: sk_live_your_key_here"

# 2020 epoch
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=48.7&lat_max=49.0&lon_min=2.2&lon_max=2.5&\
time_start=2020-01&time_end=2020-01&\
variables=ghsl_built&format=stats" \
  -H "X-API-Key: sk_live_your_key_here"

MODIS Land Cover

NASA MODIS MCD12Q1 annual land cover product — urban/built-up fraction at 0.005° spatial resolution (~500 m). Annual from 2013 to present. Global coverage.

VariableParameter valueUnitDescription
Urban fractionmodis_urbanfraction (0–1)Urban and built-up land cover fraction from IGBP classification. 0 = no urban, 1 = fully urbanised cell. Annual composites.
  • Resolution: 0.005° (~500 m) — same grid as VIIRS and GHSL
  • Coverage: global, annual, 2013–present
  • Querying a sub-annual time range returns the year's annual value for all months in that range
  • Combine with GHSL for a cross-validated urbanisation picture: GHSL (building footprints) vs MODIS (spectral land use classification)

Example: urban expansion in a growing city

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=23.0&lat_max=24.0&lon_min=113.0&lon_max=114.0&\
time_start=2013-01&time_end=2023-12&\
variables=modis_urban&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_your_key_here"

Example: urbanisation vs air quality

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=23.0&lat_max=24.0&lon_min=113.0&lon_max=114.0&\
time_start=2015-01&time_end=2023-12&\
variables=modis_urban,no2&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_your_key_here"

MODIS Burned Area (Wildfire)

NASA MODIS MCD64A1 monthly burned area composites — burned fraction per 0.005° cell (~500 m) derived from Terra+Aqua combined monthly composites. Global coverage from 2020 to present.

VariableParameter valueUnitDescription
Burned area fractionmodis_burned_areafraction (0–1)Fraction of each 0.005° cell detected as burned during the calendar month. 0 = unburned, 1 = fully burned. Sparse: only months/tiles with detected fire events are stored.
  • Resolution: 0.005° (~500 m) — same grid as VIIRS and GHSL
  • Coverage: global, monthly, 2020–present (~2 month lag for LAADS DAAC release)
  • Sparse tiles: tiles with no detected fires are not stored. Querying a fire-free area returns no rows (not an error)
  • Source: NASA LAADS DAAC MCD64A1 Collection 6.1 (CC BY 4.0)
  • Use cases: wildfire risk exposure screening, CSRD ESRS E1 physical climate risk, reinsurance, supply chain disruption modelling

Example: wildfire exposure for a site in California

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=37.0&lat_max=38.0&lon_min=-122.0&lon_max=-121.0&\
time_start=2020-01&time_end=2024-12&\
variables=modis_burned_area&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_your_key_here"

Example: fire events near an EU supply chain facility

bash
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=37.5&lat_max=39.5&lon_min=20.0&lon_max=23.0&\
time_start=2021-01&time_end=2023-12&\
variables=modis_burned_area&format=csv&aggregate=monthly" \
  -H "X-API-Key: sk_live_your_key_here"

Data Coverage

GET /api/v1/coverage

Returns the full month-by-month data availability index for CAMS and VIIRS. No authentication required.

bash
curl https://api.jiskta.com/api/v1/coverage
json
{
  "status": "ok",
  "validated_lag_months": 18,
  "interim_lag_months": 4,
  "coverage": {
    "nitrogen_dioxide": {
      "2023-01": { "type": "reanalysis",        "downloaded_at": "2026-02-01T06:12:04Z", "size_mb": 187.4 },
      "2025-10": { "type": "interim_reanalysis", "downloaded_at": "2026-02-01T06:18:31Z", "size_mb": 191.2 }
    }
  }
}

The interactive visual calendar is available on the Coverage page (no login required).

Health Check

GET /health

Check if the API is running. No authentication required.

json
{
  "status": "healthy",
  "service": "climate-api",
  "auth_enabled": true
}

Tile Availability

GET /api/v1/tiles

Returns which tiles (lat/lon grid cells, variables, and years) are available on disk for a given bounding box. Useful for checking data coverage before building a query. No authentication required.

ParameterTypeRequiredDescription
lat_min, lat_max, lon_min, lon_maxfloatoptionalBounding box filter. Default: full coverage (all tiles returned).
variablesstringoptionalComma-separated variable filter. Default: all variables.
bash
curl "https://api.jiskta.com/api/v1/tiles?lat_min=48&lat_max=53&lon_min=0&lon_max=5&variables=no2"
json
{
  "tiles": [
    {
      "lat": 50, "lon": 0,
      "variable": "no2",
      "dir": "tiles_v3",
      "years": ["2013", "2014", ..., "2025"]
    },
    ...
  ]
}

OpenAPI Specification

GET /api/v1/openapi.json

Machine-readable OpenAPI 3.0.3 specification. Use it to auto-generate clients, import into Postman/Insomnia, or explore with Swagger UI. No authentication required.

bash
curl https://api.jiskta.com/api/v1/openapi.json

Point Enrichment — NUTS3, Water Risk & Natura 2000

GET /api/v1/enrich

Given a geographic coordinate, returns the NUTS3 administrative region that contains it — along with region metadata, Eurostat socioeconomic indicators (GDP per capita, population density, respiratory mortality), WRI Aqueduct 4.0 global water risk scores (baseline water stress, water depletion, flood risk, and drought risk), and Natura 2000 protected area proximity (EU only). Useful for labelling grid query results, geocoding location inputs, or enriching tabular data with regional, water risk, and biodiversity context.

ParameterTypeRequiredDescription
latfloatLatitude (decimal degrees, WGS-84)
lonfloatLongitude (decimal degrees, WGS-84)
yearintegerReference year for Natura 2000 time series (2004–2024). Omit for the latest (2024) network. Useful for CSRD baseline assessments or research into how the protected area network evolved over time.
Example: enrich Berlin (latest Natura 2000)
curl "https://api.jiskta.com/api/v1/enrich?lat=52.52&lon=13.40"
Example: Natura 2000 network in 2010
curl "https://api.jiskta.com/api/v1/enrich?lat=51.5&lon=5.4&year=2010"
Response (EU point)
{
  "nuts3_id": "DE300",
  "nuts3_name": "Berlin",
  "country": "DE",
  "lat": 52.5,
  "lon": 13.4,
  "gdp_pps": 48700.00,
  "pop_density_km2": 4311.60,
  "resp_deaths_nr": null,
  "water_stress": {
    "bws": 2, "bws_score": 2.14, "bws_label": "Low-Medium",
    "bwd": 1, "bwd_score": 1.32, "bwd_label": "Low",
    "rfr": 3, "rfr_score": 3.21, "rfr_label": "Medium-High",
    "drr": 1, "drr_score": 1.43, "drr_label": "Low",
    "source": "WRI Aqueduct 4.0 (2023)"
  },
  "natura_2000": {
    "in_natura": true,
    "dist_km": 0.0,
    "sitecode": "BE2400008",
    "sitename": "Zoniënwoud",
    "sitetype": "SPA / Special Area of Conservation",
    "year_used": 2024,
    "year_spa": 1995,
    "year_sac": 2003,
    "source": "EEA Natura 2000 (2024)",
    "attribution": "European Environment Agency"
  }
}
Response (non-EU point — water risk only)
{
  "lat": 31.23, "lon": 121.47,
  "water_stress": {
    "bws": 5, "bws_score": 4.92, "bws_label": "Extremely High",
    "bwd": 4, "bwd_score": 4.17, "bwd_label": "High",
    "rfr": 4, "rfr_score": 4.31, "rfr_label": "High",
    "drr": 2, "drr_score": 1.88, "drr_label": "Low-Medium",
    "source": "WRI Aqueduct 4.0 (2023)"
  }
}
FieldSourceDescription
nuts3_idNUTS 2021EU NUTS3 region code (e.g. DE300). EU only.
nuts3_nameNUTS 2021Official region name
countryNUTS 2021ISO 2-letter country code
gdp_ppsEurostat nama_10r_3gdpGDP per capita in PPS (EU27=100), most recent year available. null if not published at NUTS3 level for this region.
pop_density_km2Eurostat demo_r_d3densPopulation per km², most recent year. null if unavailable.
resp_deaths_nrEurostat hlth_cd_aroAbsolute respiratory disease deaths (ICD-10 J chapter). Sparse — only countries that report at NUTS3 level.
water_stress.bwsWRI Aqueduct 4.0Baseline Water Stress integer category 1–5 (Low … Extremely High). bws_score: continuous float (v2 index only). ESRS E3-1 §27. Global.
water_stress.bwdWRI Aqueduct 4.0Baseline Water Depletion integer category 1–5. bwd_score: continuous float (v2 index only). ESRS E3-1 §27. Global.
water_stress.rfrWRI Aqueduct 4.0Riverine Flood Risk integer category 1–5. rfr_score: continuous float (v2 index only). ESRS E3-3 §38. Global.
water_stress.drrWRI Aqueduct 4.0Drought Risk integer category 1–5. drr_score: continuous float (v2 index only). ESRS E3-3 §38. Global.
natura_2000.in_naturaEEA Natura 2000 (2004–2024)Whether the coordinate is inside a Natura 2000 protected area (SPA or SAC/SCI) in the requested year. EU only; field absent for non-EU points. ESRS E4-2 §30.
natura_2000.dist_kmEEA Natura 2000 (2004–2024)Distance in km to the nearest Natura 2000 boundary for the requested year. 0.0 if inside. Supports ESRS E4-5 §40 material site biodiversity footprint disclosure. EU only.
natura_2000.year_usedEEA Natura 2000 time seriesThe actual year of the N2K raster used (floor to nearest available year in 2004–2024). Confirms which network snapshot was applied.
natura_2000.year_spaEEA N2K BackboneYear the containing or nearest site was first submitted as an SPA. 0 if unknown. Present when N2KS v2 sites index is loaded.
natura_2000.year_sacEEA N2K BackboneYear the site was first submitted as a SAC/SCI. 0 if unknown. Present when N2KS v2 sites index is loaded.
natura_2000.sitecodeEEA Natura 2000EU standard site code of the containing site (when in_natura: true), e.g. BE2400008. Present when N2KS sites index is loaded.
natura_2000.sitenameEEA Natura 2000Official name of the containing Natura 2000 site, in the national language. Present when in_natura: true.
natura_2000.sitetypeEEA Natura 2000Human-readable designation: Special Protection Area (SPA), Special Area of Conservation (SAC), or SPA / Special Area of Conservation. Present when in_natura: true.
natura_2000.nearest_sitecodeEEA Natura 2000Site code of the nearest Natura 2000 site (when in_natura: false). Present when N2KS sites index is loaded and a site is found within 100 km.
natura_2000.nearest_sitenameEEA Natura 2000Official name of the nearest Natura 2000 site. Present when in_natura: false.
natura_2000.nearest_sitetypeEEA Natura 2000Designation of the nearest site (SPA / SAC / SPA+SAC). Present when in_natura: false.
natura_2000.border_cellEEA Natura 2000 (CSRD)true when the point is outside but within 15 km of a Natura 2000 boundary. Supports ESRS E4-5 §40 material site proximity disclosure. Only emitted when true; omitted when false.
natura_2000.year_confidenceEEA N2K Backbone (CSRD)Confidence of the year_spa/year_sac designation date. "exact" = year confirmed from EEA Backbone (post-2004 submission); "baseline_proxy" = site existed before the earliest Backbone record (year set to 2004 as conservative proxy — CSRD-safe); "unknown" = no designation date available. Only emitted when N2KS v2 sites index is loaded and a nearest site was found.
natura_2000.proximity_bandEEA Natura 2000 (CSRD)Categorical distance band for ESRS E4 materiality assessment. One of: "inside", "0-1km", "1-5km", "5-15km", "15-50km", ">50km". EU only. Always present alongside in_natura.
natura_2000.spatial_confidenceEEA Natura 2000 (CSRD)Reliability of the inside/outside classification at 0.1° raster resolution. "likely_inside" = classified inside (0.1° cell may straddle the boundary — verify for CSRD); "high" = clearly outside (dist ≥ 5 km or no Natura 2000 neighbours); "uncertain" = within 5 km border zone (cell may be mis-classified); "uncertain_lean_inside" = in border zone but surrounded by Natura 2000 cells (likely inside). EU only.
natura_2000.neighbors_in_naturaEEA Natura 2000 (CSRD)Count of the 8 surrounding 0.1° raster cells that are inside Natura 2000 for the requested year. Ranges 0–8. Supports spatial confidence calibration: ≥6 neighbours suggests the point is on the Natura 2000 network even if the exact cell is classified outside. EU only; present when a site is found.
natura_2000.csrd_evidenceEEA Natura 2000 (CSRD)Structured ESRS E4 evidence block. Present when the point is inside Natura 2000 or within 15 km (border_cell). Fields: e4_trigger (string, e.g. "inside" or "border_cell"), proximity_band, spatial_confidence, verification_recommended (boolean — true when independent GIS verification is advisable before reporting). Omitted when the point is clearly outside.
natura_2000.n_habitats_annex1EEA Natura 2000 Annex I (CSRD)Total count of Annex I habitat types listed for the containing or nearest Natura 2000 site. Annex I habitats are legally protected EU habitat types under the Habitats Directive (92/43/EEC). Relevant for ESRS E4-2 §30 biodiversity and ecosystem services disclosures. Present when the N2KH habitat index is loaded and a site is found.
natura_2000.has_priority_habitatEEA Natura 2000 Annex I (CSRD)true if the site contains at least one priority habitat type (marked with * in the Habitats Directive Annex I — e.g. alluvial forests 91E0*, Mediterranean maquis, bog woodlands). Priority habitats trigger enhanced CSRD/ESRS E4 disclosure obligations under §44 and may require Environmental Impact Assessment. Present alongside n_habitats_annex1.
natura_2000.habitat_codesEEA Natura 2000 Annex I (CSRD)Array of up to 5 representative Annex I habitat type codes (integers) listed for the site, sorted ascending. Full list available on EUNIS (e.g. 3150 = natural eutrophic lakes, 6230 = species-rich Nardus grasslands). Capped at 5 in the API response; n_habitats_annex1 gives the total count. Present when the N2KH habitat index is loaded.
natura_2000.n_species_annex2EEA Natura 2000 Annex II (CSRD)Total count of Habitats Directive Annex II species listed for the containing or nearest Natura 2000 site. Annex II species are plants and animals whose conservation requires the designation of Special Areas of Conservation (SACs). Omitted when the site has no Annex II species records. Relevant for ESRS E4-4 §38 biodiversity impact disclosures.
natura_2000.species_groupsEEA Natura 2000 Annex II (CSRD)Array of species group letters present at the site. Values: "B" = Birds (Annex I of the Birds Directive), "M" = Mammals, "R" = Reptiles, "A" = Amphibians, "F" = Fish, "I" = Invertebrates, "P" = Plants. Example: ["M","R","F","I","P"]. Omitted when n_species_annex2 is absent. Together with n_species_annex2, this supports the CSRD biodiversity sensitivity screening required under ESRS E4 for sites inside or near Natura 2000.

Coverage: NUTS3 — EU only (1,514 regions). Water risk — global (WRI Aqueduct 4.0, catchment polygons rasterized to 0.1°). Natura 2000 — EU only, time series 2004–2024 (21 annual rasters, 27,295 sites, 0.1° rasterized with distance transform). The ?year= parameter selects the network snapshot; omit for latest (2024). Points outside EU still receive water risk data. No credits are deducted. No authentication required.

Natura 2000 CSRD fields (proximity_band, spatial_confidence, neighbors_in_natura, csrd_evidence, n_habitats_annex1, has_priority_habitat, habitat_codes, n_species_annex2, species_groups) require both the N2K sites index and the Annex I/II habitat+species binary to be deployed on the server. All fields are present in the production API at api.jiskta.com.

E-PRTR Facilities

GET /api/v1/facilities

Search ~60,000 verified EU industrial facilities from the European Pollutant Release and Transfer Register (E-PRTR). Each facility has a verified GPS coordinate, E-PRTR sector classification, and annual self-reported emissions data (NOₓ, PM10, PM2.5, CO₂) for 2007–2022. No authentication required.

ParameterTypeRequiredDescription
qstringoptional*Text search on facility name or operator. Case-insensitive, partial match. E.g. q=BASF, q=ThyssenKrupp.
countrystringoptionalFilter by ISO 2-letter country code. E.g. country=DE. Can be combined with q.
latfloatoptional*Latitude for radius search. Requires lon and radius_km.
lonfloatoptional*Longitude for radius search.
radius_kmfloatoptionalSearch radius in kilometres around lat/lon. Default: 25 km.
idstringoptional*Direct lookup by inspire_hash (64-bit integer as string). Returns the single matching facility or 404.
emissionsbooleanoptionaltrue — include annual NOₓ / PM10 / PM2.5 / CO₂ emission data (2007–2022) in the response. Increases response size. Default: false.

* At least one of q, lat/lon, or id is required.

bash
# Search by name + country
curl "https://api.jiskta.com/api/v1/facilities?q=BASF&country=DE"

# Radius search — facilities within 20 km of Ghent, Belgium
curl "https://api.jiskta.com/api/v1/facilities?lat=51.05&lon=3.72&radius_km=20"

# Direct ID lookup with emissions history
curl "https://api.jiskta.com/api/v1/facilities?id=12626246128710077482&emissions=true"
json
{
  "status": "ok",
  "count": 2,
  "facilities": [
    {
      "inspire_hash": "12626246128710077482",
      "name": "BASF SE Ludwigshafen",
      "operator": "BASF SE",
      "country": "DE",
      "sector": "Chemical industry",
      "lat": 49.517,
      "lon": 8.423,
      "nox_emissions": {
        "2015": 12450, "2016": 11820, "2017": 10930,
        "2018": 10210, "2019": 9870,  "2020": 8640,
        "2021": 8120,  "2022": 7890
      }
    },
    ...
  ]
}
Response fieldDescription
inspire_hashStable 64-bit integer ID (string). Use as the facility= parameter in climate queries.
nameOfficial facility name from E-PRTR
operatorLegal operator / parent company name
countryISO 2-letter country code
sectorE-PRTR sector classification (e.g. Energy, Metal, Chemical, Mineral, Waste)
lat / lonVerified GPS coordinates (WGS-84). These are the coordinates used when facility= is passed to a climate query.
nox_emissionsAnnual self-reported NOₓ emissions in tonnes, keyed by year (2007–2022). Only present when emissions=true.

Use case: Look up a facility's inspire_hash, then use it in a climate query to get satellite-verified air quality trends — optionally cross-validated against the self-reported emissions via divergence analysis. The Query Builder Facilities tab provides an interactive search UI with one-click query integration.

Water Risk Grid

GET https://api.jiskta.com/api/v1/water-risk

Returns WRI Aqueduct 4.0 (2023) water risk scores for every 0.1° grid cell in a bounding box. Useful for researchers correlating water risk with air quality or economic data, and for CSRD portfolio screening across large geographic areas.

Auth: X-API-Key header required. Credits: 0 — static dataset, no time dimension.

Max bbox: 50°×50° (250,000 cells). Resolution: 0.1°.

ParamRequiredDescription
lat_min, lat_max, lon_min, lon_maxBounding box (WGS-84). Max 50°×50°.
formatjson (default) or csv
labelstrue — include text labels (e.g. "Extremely High") alongside numeric scores
varsComma-separated: bws, bwd, rfr, drr (default: all four)
Request — JSON format with labels
curl "https://api.jiskta.com/api/v1/water-risk?lat_min=25&lat_max=26&lon_min=55&lon_max=56&format=json&labels=true" \
  -H "X-API-Key: sk_live_..."
Response (JSON)
{
  "status": "ok",
  "source": "WRI Aqueduct 4.0 (2023)",
  "n_cells": 100,
  "has_scores": true,
  "cells": [
    { "lat": 25.05, "lon": 55.15,
       "bws": 5, "bws_score": 4.92, "bws_label": "Extremely high",
       "bwd": 5, "bwd_score": 5.11, "bwd_label": "Extremely high",
       "rfr": 4, "rfr_score": 4.31, "rfr_label": "High",
       "drr": null, "drr_score": null, "drr_label": "No data" },
    ...
  ]
}
Request — CSV format (for pandas/R)
curl "https://api.jiskta.com/api/v1/water-risk?lat_min=48&lat_max=52&lon_min=2&lon_max=8&format=csv" \
  -H "X-API-Key: sk_live_..."
Response (CSV)
lat,lon,bws,bws_score,bwd,bwd_score,rfr,rfr_score,drr,drr_score
48.05,2.05,,,,,,,,
48.05,2.15,2,2.140,1,1.320,3,3.210,,
48.05,2.25,2,2.140,2,1.980,3,3.210,2,1.880
...

Score legend: null / empty = no catchment data. 1 = Low, 2 = Low-Medium, 3 = Medium-High, 4 = High, 5 = Extremely High. When has_scores: true (v2 index), each indicator also includes a continuous raw score (bws_score, bwd_score, rfr_score, drr_score) on a 1.0–5.0+ float scale — more useful than integer categories for Pearson correlation, ranking within same category, and precise threshold analysis. Raw scores are absent with the v1 index. Relevant for CSRD ESRS E3-1 §27 (water stress) and E3-3 §38 (flood & drought risk).

Geocoding

GET /api/v1/geocode

High-performance global geocoding built on the Jiskta open-data engine — 467 million housenumbers, 62.7 million streets, 394,000 cities, 1.85 million postcodes sourced from OpenStreetMap. Supports forward geocoding (address → coordinates) and reverse geocoding (coordinates → address). 0.01 credits per call.

The engine uses io_uring O_DIRECT for zero-RAM lookups against a 1.6 GB binary index served from local NVMe — no external API calls, no rate limits, typically <1 ms response time.

ParameterTypeRequiredDescription
qstringone of q or lat+lonAddress string for forward geocoding. Format: Street Name [Number], City[, Postcode][, Country]. Country can be ISO2 code or full name.
latfloatwith lonLatitude for reverse geocoding (WGS-84)
lonfloatwith latLongitude for reverse geocoding (WGS-84)
Forward geocode — address to coordinates
curl "https://api.jiskta.com/api/v1/geocode?q=Rue+de+Rivoli+15,+Paris" \
  -H "X-API-Key: sk_live_your_key"
Reverse geocode — coordinates to address
curl "https://api.jiskta.com/api/v1/geocode?lat=48.8599&lon=2.3477" \
  -H "X-API-Key: sk_live_your_key"
Response
{
  "lat": 48.859932,
  "lon": 2.347700,
  "confidence": 1.00,
  "street": "Rue de Rivoli",
  "city": "Paris",
  "postcode": "75001",
  "country": "FR",
  "source": "housenumber",
  "fallback": ""
}
FieldDescription
lat / lonResult coordinates (WGS-84)
confidenceMatch quality: 1.0 = exact housenumber, 0.7 = street centroid, 0.5 = postcode centroid, 0.3 = city centroid, 0.1 = country centroid
sourceWhat was matched: housenumber, street_centroid, postcode_centroid, city_centroid, country_centroid, reverse_stub
fallbackWhy a lower confidence level was used: no_housenumber, no_street, no_city, no_country, non_latin, or empty string on exact match
streetMatched street name
cityMatched city name
postcodeMatched postcode
countryISO 2-letter country code

Coverage: Global — 467M housenumbers from OpenStreetMap across all continents. Dense coverage in Western Europe, North America, and Australia. Sparser in parts of Asia and Africa where OSM housenumber data is less complete. Response time: typically <1 ms (io_uring O_DIRECT from local NVMe). Auth: X-API-Key header required. Credits: 0.01 per call.

Aggregates raster climate data to EU administrative regions and returns per-region statistics along with optional cross-dataset computations (correlation, regression, top-N). Coverage is restricted to the EU domain (lat 30–72°N, lon 25°W–45°E). Ideal for ESG reporting, epidemiological analysis, policy dashboards, and academic research.

Request body (JSON)
{
  "region": "DE",
  "time_range": { "start": "2022-01", "end": "2022-12" },
  "datasets": [
    { "name": "no2", "source": "cams_no2" },
    { "name": "pm25", "source": "cams_pm2p5" }
  ],
  "compute": [
    { "op": "mean", "input": "no2",  "output": "no2_mean"  },
    { "op": "mean", "input": "pm25", "output": "pm25_mean" },
    { "op": "pearson_r", "x": "no2", "y": "pm25",
       "output": "r_no2_pm25", "include_n": true }
  ]
}

Request fields

FieldTypeRequiredDescription
regionstring✓ *Named EU region preset: "DE", "FR", "EU", ISO2 country code, etc. Use region or bbox.
bboxobject✓ *Bounding box: lat_min, lat_max, lon_min, lon_max. Must be within EU domain (lat 30–72°N, lon 25°W–45°E).
time_rangeobjectDate range: start and end (YYYY-MM-DD or YYYY-MM)
datasetsarrayList of raster sources. Each entry: name (your alias) + source (see table below)
resolutionstring"nuts3" (default) | "country"
computearrayCross-dataset operations (see below)

Dataset sources

CAMS sources auto-route to EU 0.1° tiles inside Europe and Global 0.75° tiles outside. Use explicit cams_eu_* / cams_global_* aliases to override.

SourceVariableUnitCoverage
📡 CAMS Air Quality — auto-routing
cams_no2Nitrogen dioxideµg/m³EU 0.1° / Global 0.75° (auto)
cams_pm2p5PM2.5 particlesµg/m³EU 0.1° / Global 0.75° (auto)
cams_pm10PM10 particlesµg/m³EU 0.1° / Global 0.75° (auto)
cams_o3Ozoneµg/m³EU 0.1° / Global 0.75° (auto)
📡 CAMS Air Quality — explicit EU 0.1° (2013–present)
cams_eu_no2Nitrogen dioxideµg/m³EU only, 0.1°
cams_eu_pm2p5PM2.5 particlesµg/m³EU only, 0.1°
cams_eu_pm10PM10 particlesµg/m³EU only, 0.1°
cams_eu_o3Ozoneµg/m³EU only, 0.1°
🌍 CAMS Air Quality — explicit Global 0.75° (2020–present)
cams_global_no2 / global_no2Nitrogen dioxideµg/m³Global, 0.75°
cams_global_pm2p5 / global_pm2p5PM2.5 particlesµg/m³Global, 0.75°
cams_global_pm10 / global_pm10PM10 particlesµg/m³Global, 0.75°
cams_global_o3 / global_o3Ozoneµg/m³Global, 0.75°
🌤 ERA5 Meteorology (EU domain, 0.25°, 2013–present)
era5_t2m / t2m2m temperatureKEU domain, 0.25°
era5_blh / blhBoundary layer heightmEU domain, 0.25°
era5_tp / tpTotal precipitationm/hEU domain, 0.25°
era5_u10 / u1010m U-windm/sEU domain, 0.25°
era5_v10 / v1010m V-windm/sEU domain, 0.25°
🌃 VIIRS Nighttime Lights (global, 0.005°, 2020–present)
viirs_radiance / viirs / nightlightsNighttime radiancenW/cm²/srGlobal, 0.005°
🏭 Human Settlement (global snapshots)
ghsl_popPopulation densitypersons/cellGlobal
ghsl_builtBuilt-up surface (total)Global
ghsl_built_nresBuilt-up surface (non-res)Global
modis_urbanUrban land cover fractionfraction 0–1Global
🔥 MODIS Burned Area (global, 0.005°, 2020–present)
modis_burned_areaMonthly burned area fractionfraction 0–1Global, 0.005° (~500 m). MODIS MCD64A1 (CC BY 4.0). Monthly, 2020–present. Sparse.
🌍 Fossil-Fuel CO₂ Emissions
odiac_co2Fossil-fuel CO₂ emissionsgC/m²/monthGlobal, 0.01° (~1 km). ODIAC2024 (CC BY 4.0). 2020–2023.
🌿 CAMS GHG Inversion — Greenhouse Gas Surface Flux (1°, global, monthly, 2016–present)
ghg_co2 / atmospheric_co2CO₂ net surface fluxkgC/m²/monthGlobal, 1°. Biogenic + ocean + fossil net flux. ECMWF CAMS Inversion (CarbonTracker-EU). Copernicus open data. ESRS E1.
ghg_ch4 / atmospheric_ch4 / methaneCH₄ surface fluxkg/m²/sGlobal, 1°. Total methane emissions (agriculture, wetlands, fossil). ECMWF CAMS Inversion. Copernicus open data. ESRS E1.
ghg_n2oN₂O surface fluxkgN/m²/monthGlobal, ~0.7°. Nitrous oxide (agriculture, soils). ECMWF CAMS Inversion. Copernicus open data. ESRS E1.
💧 WRI Aqueduct 4.0 Water Risk (static, global, 0.1°)
aqueduct_bws / water_stressBaseline Water Stressscore 1–5Global. Low (1) … Extremely High (5). ESRS E3-1 §27.
aqueduct_bwd / water_depletionBaseline Water Depletionscore 1–5Global. Low (1) … Extremely High (5). ESRS E3-1 §27.
aqueduct_rfr / flood_riskRiverine Flood Riskscore 1–5Global. Low (1) … Extremely High (5). ESRS E3-3 §38.
aqueduct_drr / drought_riskDrought Riskscore 1–5Global. Low (1) … Extremely High (5). ESRS E3-3 §38.

Compute operations

opFieldsOutputDescription
meaninputscalarMean across all NUTS3 units
suminputscalarSum across all NUTS3 units
min / maxinputscalarMin / max value
countinputintegerNumber of NUTS3 units with data
pearson_rx, y, include_nobject: r, r2, nCross-region Pearson correlation
top_ninput, n, directionarrayTop/bottom N regions by value
Response
{
  "status": "success",
  "query_time_ms": 2,
  "credits_used": 6,
  "credits_remaining": 14800,
  "n_units": 255,
  "spatial_resolution": "nuts3",
  "n_raster_cols": 2,
  "no2_mean": 9.62,
  "pm25_mean": 8.69,
  "r_no2_pm25": { "r": 0.553, "r2": 0.306, "n": 255, "interpretation": "moderate positive" },
  "units": [
    { "nuts3_id": "FR101", "nuts3_name": "Paris", "country": "FR",
       "no2": 24.56, "pm25": 14.8, "n_cells": 1 },
    ...
  ]
}

Credits: 1 credit per dataset per tile scanned. Germany (4 tiles) with 2 CAMS datasets for 12 months costs 96 credits.

Coverage: EU domain (lat 30–72°N, lon 25°W–45°E). CAMS EU tiles at 0.1°, ERA5 at 0.25°, 2013–present. NUTS3 resolution (1,514 regions) or country resolution. Max 500 tile reads per request — use country-level queries or reduce time range for larger regions.

Redeem Voucher

POST /api/v1/redeem

Redeem a voucher code to add credits to your account. Requires authentication via X-API-Key header.

Request

bash
curl -X POST https://api.jiskta.com/api/v1/redeem \
  -H "X-API-Key: sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"code": "WELCOME-2025-ABC"}'

Request Body

json
{
  "code": "WELCOME-2025-ABC"
}

Response

json
{
  "status": "success",
  "credits_added": 500,
  "credits_remaining": 500,
  "description": "Welcome bonus"
}
StatusMeaning
200Voucher redeemed successfully
401Invalid or missing API key
404Invalid voucher code
409Voucher already redeemed
410Voucher has expired

Buy Credits

POST /api/v1/checkout

Create a Paddle checkout session to purchase credits. A Paddle payment overlay opens on the dashboard — Paddle handles VAT and billing for all countries automatically.

For EU B2B customers: include vat_number to enable reverse charge (0% VAT). Pre-fill these from your Billing Details on the dashboard.

Request Body

json
{
  "package": "starter",
  "api_key_id": "your-api-key-uuid",
  "company_name": "Acme Corp",
  "vat_number": "NL123456789B01",
  "billing_country": "NL"
}
FieldTypeRequiredDescription
package string One of: starter (€10), professional (€50), enterprise (€200)
api_key_id string Your API key UUID (from account setup)
company_name string Legal company name — printed on the Paddle invoice
vat_number string EU VAT ID (e.g. NL123456789B01). Paddle validates against VIES and applies reverse charge (0% VAT) for valid EU B2B numbers.
billing_country string ISO 3166-1 alpha-2 country code (e.g. NL, DE, FR). Used by Paddle for correct tax calculation.

Response

json
{
  "transaction_id": "txn_01abc..."
}

The dashboard uses this transaction_id to open the Paddle checkout overlay via Paddle.js. Credits are added automatically after successful payment.

Regenerate API Key

If your API key is compromised or you need a fresh key, you can regenerate it from the Dashboard.

How it works

  • Click "Regenerate Key" on the dashboard
  • Your old key is immediately deactivated — any requests using it will return 401
  • A new sk_live_ key is generated
  • Your credit balance is fully preserved (transferred to the new key)
  • The new key is shown once — copy it and store it safely

Technical Details (Supabase RPC)

For advanced integrations, the regeneration is powered by a Supabase RPC function called from the authenticated client:

javascript
const { data, error } = await supabase.rpc('regenerate_api_key');
// Returns: [{ raw_key: "sk_live_...", new_prefix: "sk_live_abcd" }]

Important: This function requires an authenticated Supabase session. It uses auth.email() to identify the user, so it can only regenerate your own key.

Python SDK

Install with pip:   PyPI   ⭐ GitHub

bash
pip install jiskta
pip install "jiskta[pandas]"   # optional — returns pandas DataFrames

Daily data over Paris, 2023

python
from jiskta import JisktaClient

client = JisktaClient(api_key="sk_live_your_key_here")

# Daily NO₂ and PM2.5 over Paris, full year 2023
df = client.query(
    lat=(48.7, 49.0),
    lon=(2.2, 2.5),
    start="2023-01",
    end="2023-12",
    variables=["no2", "pm2p5"],
    aggregate="daily",
)
print(df.head())
#         lat    lon        date  no2_mean  pm2p5_mean
# 0  48.7500  2.250  2023-01-01     12.34        8.21

Summary statistics and exceedance

python
# Stats (no DataFrame needed)
result = client.stats(
    area="paris",
    start="2023-01",
    end="2023-12",
    variables=["no2"],
)
print(result["output"])
# Rows matched: 8760\nMin: 1.2\nMax: 78.4\nAverage: 14.3

# Hours above WHO guideline (25 µg/m³)
df = client.query(
    lat=(48.7, 49.0), lon=(2.2, 2.5),
    start="2023-01", end="2023-12",
    variables=["no2"],
    aggregate="exceedance",
    threshold=25.0,
)
print(df.columns.tolist())
# ['lat', 'lon', 'hours_above', 'total_hours', 'pct_above']

Error handling

python
from jiskta import JisktaClient, AuthError, InsufficientCreditsError, RateLimitError

try:
    df = client.query(...)
except AuthError:
    print("Invalid API key")
except InsufficientCreditsError:
    print("Buy more credits at https://jiskta.com/pricing")
except RateLimitError:
    print("Server busy — retry with exponential backoff")

Node.js SDK

Install with npm (zero runtime dependencies):   npm   ⭐ GitHub

bash
npm install jiskta

Daily data over Paris, 2023

javascript
import { JisktaClient } from "jiskta";
// const { JisktaClient } = require("jiskta");  // CommonJS

const client = new JisktaClient("sk_live_your_key_here");

// Daily NO₂ and PM2.5 over Paris, full year 2023
const rows = await client.query({
  lat: [48.7, 49.0],
  lon: [2.2, 2.5],
  start: "2023-01",
  end: "2023-12",
  variables: ["no2", "pm2p5"],
  aggregate: "daily",
});
console.log(rows[0]);
// { lat: 48.75, lon: 2.25, date: '2023-01-01', no2_mean: 12.34, pm2p5_mean: 8.21 }

Point query and monthly stats

javascript
// Single grid cell — pass a number instead of [min, max]
const rows = await client.query({
  lat: 48.85,   // snaps to nearest 0.1° grid cell
  lon: 2.35,
  start: "2023-01",
  end: "2023-12",
  variables: ["no2"],
  aggregate: "monthly",
});

// Summary stats
const result = await client.stats({
  lat: [48.7, 49.0], lon: [2.2, 2.5],
  start: "2023-01", end: "2023-01",
  variables: ["no2"],
});
console.log(result.output);
// "Rows matched: 744\nMin: 5.2\nMax: 38.1\nAverage: 14.3"

Error handling

javascript
import { JisktaClient, AuthError, InsufficientCreditsError, RateLimitError } from "jiskta";

try {
  const rows = await client.query({ ... });
} catch (err) {
  if (err instanceof AuthError) console.error("Invalid API key");
  else if (err instanceof InsufficientCreditsError) console.error("Buy more credits");
  else if (err instanceof RateLimitError) console.error("Retry after a short wait");
}

MCP Server

Use Jiskta directly inside AI assistants — Claude Desktop, Cursor, Cline, and any other tool that supports Model Context Protocol. The server runs on your machine and calls the Jiskta API using your key. Zero setup with uvx.

json
// claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "jiskta": {
      "command": "uvx",
      "args": ["jiskta-mcp"],
      "env": { "JISKTA_API_KEY": "sk_live_..." }
    }
  }
}

Once connected, ask things like "What was PM2.5 in Brussels in 2023?" or "Is there industrial pollution risk near this address?"

Available tools: query_climate, query_climate_point, estimate_query_cost, geocode, reverse_geocode, enrich_location, water_risk, find_facilities, get_coverage, spatial_link.

📦 PyPI    ⭐ GitHub

Jupyter Notebooks & Examples

Ready-to-run examples covering air quality analysis, ERA5 climate trends, exceedance mapping, and a Node.js interactive map explorer:

cURL

bash
# Hourly PM2.5 in London, March 2023 — save to CSV
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=51.3&lat_max=51.7&\
lon_min=-0.5&lon_max=0.3&\
time_start=2023-03-01&time_end=2023-04-01&\
variables=pm2p5&format=csv&aggregate=daily" \
  -H "X-API-Key: sk_live_your_key_here" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['output'])" > london_pm25_march.csv

# Exceedance: hours of NO₂ above WHO guideline (25 µg/m³), Berlin, 2023
curl "https://api.jiskta.com/api/v1/climate/query?\
lat_min=52.3&lat_max=52.7&\
lon_min=13.0&lon_max=13.7&\
time_start=2023-01-01&time_end=2023-12-31&\
variables=no2&threshold=25" \
  -H "X-API-Key: sk_live_your_key_here" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['output'])"

Data Quality & Coverage

CAMS data uses a three-tier quality system. ERA5 data is the definitive ECMWF global atmospheric reanalysis and is considered fully validated for all available months.

CAMS EU quality tiers

TierAPI valueDescriptionLag
Validated reanalysis Peer-reviewed, bias-corrected against ground station observations. Used as reference data in compliance and ESG reporting contexts. ~18–24 months after reference period
Interim interim_reanalysis Passes preliminary QA checks. Suitable for trend analysis and monitoring. Automatically upgraded to validated when ECMWF publishes the full reanalysis. ~4 months after reference period
NRT nrt_analysis Near Real-Time CAMS analysis. Covers the gap between interim reanalysis and today (~1–4 month lag window). Available within ~24 hours. Automatically replaced by interim/validated reanalysis when published. ~24 hours after reference period

CAMS Global quality tiers

TierAPI valueDescriptionLag
Validated (EAC4) validated_reanalysis ECMWF EAC4 global reanalysis. 0.75° resolution, 3-hourly. Highest quality global dataset. ~7 months after reference period
NRT (Global) nrt_analysis CAMS Global Atmospheric Composition Forecasts. Fills the 7-month EAC4 lag. Same 0.75° global grid. Automatically replaced by EAC4 when it becomes available. ~24 hours after reference period

The data type for a queried month is returned in the API response metadata. Queries work identically regardless of tier — you don't need to specify anything.

Coverage endpoint

GET /api/v1/coverage

Returns the full month-by-month availability index. No authentication required.

bash
curl https://api.jiskta.com/api/v1/coverage
json
{
  "status": "ok",
  "validated_lag_months": 18,
  "interim_lag_months": 4,
  "coverage": {
    "nitrogen_dioxide": {
      "2023-01": {
        "type": "reanalysis",
        "downloaded_at": "2026-02-01T06:12:04Z",
        "size_mb": 187.4
      },
      "2025-10": {
        "type": "interim_reanalysis",
        "downloaded_at": "2026-02-01T06:18:31Z",
        "size_mb": 191.2
      }
    },
    "particulate_matter_2.5um": { "..." }
  }
}

The interactive visual calendar is available on the Data Coverage page.

Automatic monthly sync

A cron job runs on the 1st of each month and:

  1. Adds any newly available interim months (ECMWF publishes ~4 months after the reference period)
  2. Replaces interim files with validated reanalysis for months that have now passed the ~18-month lag
  3. Regenerates the affected QKPT3 tiles automatically

You will never need to re-query historical data — upgrades happen transparently. If you need to know the tier of a specific month before querying, check /api/v1/coverage.

Error Handling

The API uses standard HTTP status codes:

StatusMeaningAction
200 Success Parse the response data
400 Bad Request Check your query parameters; or result exceeded 500K rows — use aggregate=daily or smaller range
401 Unauthorized Check your API key
402 Payment Required Insufficient credits — buy more
429 Too Many Requests Server under load and your key is above its fair share — retry after 500ms with exponential backoff
500 Server Error Retry after a moment; contact support if persistent

Error response format

json
{
  "error": "Insufficient credits",
  "credits_remaining": 3,
  "credits_required": 8
}