Python SDK for the Jiskta Climate Data API — query historical air quality data (NO₂, PM2.5, PM10, O₃) and ERA5 meteorological data via a simple Python interface.
pip install jisktaPandas is optional but recommended:
pip install "jiskta[pandas]"from jiskta import JisktaClient
client = JisktaClient(api_key="sk_live_...")
# Daily NO₂ and PM2.5 over Paris in 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
# ...df = client.query(
lat=(lat_min, lat_max), # bounding box
lon=(lon_min, lon_max),
start="YYYY-MM-DD", # or "YYYY-MM"
end="YYYY-MM-DD",
variables=["no2"], # no2 | pm2p5 | pm10 | o3
aggregate="daily", # hourly | daily | monthly | annual
# area_hourly | area_daily | area_monthly
# diurnal | exceedance | percentile
threshold=40.0, # µg/m³ (exceedance mode)
percentile=95, # 0-100 (percentile mode)
)result = client.stats(
lat=(48.7, 49.0),
lon=(2.2, 2.5),
start="2023-01",
end="2023-01",
variables=["no2"],
)
print(result["output"])from jiskta import JisktaClient, AuthError, InsufficientCreditsError, RateLimitError, JisktaError
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 later")
except JisktaError as e:
print(f"API error {e.status_code}: {e}")Skip lat/lon by passing a named region:
df = client.query(
area="paris",
start="2023-01",
end="2023-12",
variables=["no2"],
aggregate="daily",
)
result = client.stats(area="belgium", start="2023-01", end="2023-12")Supported names depend on the API (e.g. "paris", "france", "belgium").
# Statistical aggregates
df = client.query(..., aggregate="max") # daily/monthly max
df = client.query(..., aggregate="min") # daily/monthly min
df = client.query(..., aggregate="stddev") # standard deviation
df = client.query(..., aggregate="cumulative") # running total (e.g. precipitation)
# Analytical aggregates
df = client.query(..., aggregate="seasonal") # DJF/MAM/JJA/SON means
df = client.query(..., aggregate="trend") # linear trend per grid cellwind_speed and wind_dir are derived ERA5 variables (computed from u10/v10):
df = client.query(
lat=(48.7, 49.0), lon=(2.2, 2.5),
start="2023-01", end="2023-12",
variables=["wind_speed", "wind_dir"],
aggregate="daily",
)Pass any GeoJSON Polygon or MultiPolygon geometry to restrict results to cells
whose centres fall inside the polygon:
mask = {
"type": "Polygon",
"coordinates": [[[2.2, 48.7], [2.5, 48.7], [2.5, 49.0], [2.2, 49.0], [2.2, 48.7]]],
}
df = client.query_with_mask(
lat_min=48.7, lat_max=49.0,
lon_min=2.2, lon_max=2.5,
start="2023-01",
end="2023-12",
variables=["no2"],
aggregate="daily",
mask=mask,
)df = client.query(
lat=(48.7, 49.0), lon=(2.2, 2.5),
start="2023-01", end="2023-12",
variables=["no2"],
sort_by="no2_mean", # sort CSV output by column
sort_dir="desc", # "asc" or "desc"
unit="ppb", # convert output units
round=2, # decimal places
dry_run=True, # cost estimate only — no query executed
missing_null=True, # empty string for missing cells
include_polygon=True, # include area_polygon GeoJSON in raw response
)MIT