Query Structure
Overview
A forecast query to the data endpoint is a JSON object that specifies:
What data you want (models, variables)
Where you want it (geographic filter)
When you want it (temporal filter)
How to process it (aggregation, grouping, weighting)
How to return it (format, ordering, pagination)
Basic Query Structure
{
"models": ["ept2"],
"geo": { ... },
"init_time": "latest",
"variables": [ ... ],
"prediction_timedelta": { ... },
...
}Required Parameters
Every query must include these three parameters:
models (required)
models (required)List of forecast model identifiers to query.
Type: array of strings
Minimum length: 1
{
"models": ["ept2"]
}Multiple models:
{
"models": ["ept2", "aifs", "ecmwf_ifs_single"]
}You can find an overview of the available models here.
geo (required)
geo (required)Geographic filter specifying where to query data.
Type: object
Structure:
{
"type": "point" | "bounding_box" | "polygon" | "market_zone" | "country_key",
"value": ...,
"method": "nearest" | "bilinear" // Only for type="point"
}See Geographic Filtering section below for details.
init_time (required)
init_time (required)Forecast initialization time(s).
Type: string | array | object
Options:
Latest forecast:
{ "init_time": "latest" }Specific datetime (ISO 8601 format):
{ "init_time": "2025-10-22T00:00:00Z" } { "init_time": "2025-10-22 00:00:00" }List of datetimes:
{ "init_time": ["2025-10-22T00:00:00Z", "2025-10-22T12:00:00Z"] }Time range:
{ "init_time": { "start": "2025-10-01T00:00:00Z", "end": "2025-10-31T23:59:59Z" } }
Geographic Filtering
The geo parameter supports five types of geographic queries.
1. Point Query
Query at specific coordinate(s).
Coordinates: [latitude, longitude]
Single point:
{
"type": "point",
"value": [47.37, 8.54], // Zurich [lat, lon]
"method": "nearest" // or "bilinear"
}Multiple points:
{
"type": "point",
"value": [
[52.52, 13.4], // Berlin
[47.37, 8.54], // Zurich
[48.86, 2.35] // Paris
],
"method": "nearest"
}Interpolation methods:
"nearest"(default): Returns value from nearest grid point"bilinear": Interpolates between 4 surrounding grid points
2. Bounding Box
Query a rectangular region.
Format: [[lat_min, lon_min], [lat_max, lon_max]]
{
"type": "bounding_box",
"value": [
[45.0, 5.0],
[50.0, 15.0]
]
}This queries all grid points within the rectangle from (45°N, 5°E) to (50°N, 15°E).
Large bounding boxes return many grid points. Consider using group_by with aggregation to aggregate data over the region.
Large requests might get blocked by the request_credit_limit, which helps avoid large costs but can be increased for intentionally querying large amounts of raw data.
3. Polygon
Query data within a custom polygon area.
Format: Array of [latitude, longitude] coordinates defining the polygon boundary.
{
"type": "polygon",
"value": [
[45.82, 5.96], // Southwest corner
[45.82, 10.49], // Southeast corner
[47.81, 10.49], // Northeast corner
[47.81, 5.96], // Northwest corner
[45.82, 5.96] // Close the polygon (repeat first point)
]
}Requirements:
Minimum 4 points (3 unique vertices + closing point)
Polygon should follow counter-clockwise ordering
Polygons covering a large area return many grid points. Consider using group_by with aggregation to aggregate data over the region.
Large requests might get blocked by the request_credit_limit, which helps avoid large costs but can be increased for intentionally querying large amounts of raw data.
4. Market Zone
Query data for predefined energy market zones.
Format: String or array of market zone codes
{
"type": "market_zone",
"value": "DE" // Germany
}Multiple market zones:
{
"type": "market_zone",
"value": ["IR", "GB-NIR"]
}Market zones covering a large area return many grid points. Consider using group_by with aggregation to aggregate data over the region.
Large requests might get blocked by the request_credit_limit, which helps avoid large costs but can be increased for intentionally querying large amounts of raw data.
5. Country
Query data for entire countries.
Format: ISO country codes
{
"type": "country_key",
"value": "DE"
}Multiple countries:
{
"type": "country_key",
"value": ["DE", "FR", "US"]
}Querying countries returns many grid points. Consider using group_by with aggregation to aggregate data over the region.
Large requests might get blocked by the request_credit_limit, which helps avoid large costs but can be increased for intentionally querying large amounts of raw data.
Temporal Filtering
Control which forecast times to retrieve using temporal filters.
Forecast Time Concepts
init_time(required): When the forecast was generatedprediction_timedelta: How far ahead from init_time (lead time)time: Absolute forecast valid time (init_time + prediction_timedelta)
prediction_timedelta (optional)
prediction_timedelta (optional)Forecast lead time from initialization.
Units: Controlled by timedelta_unit parameter (default: "h" for hours)
Single value:
{
"prediction_timedelta": 24,
"timedelta_unit": "h" // 24 hours ahead
}List of values:
{
"prediction_timedelta": [0, 6, 12, 18, 24],
"timedelta_unit": "h"
}Range:
{
"prediction_timedelta": {
"start": 0,
"end": 168 // 0 to 168 hours (7 days)
},
"timedelta_unit": "h"
}If omitted: Returns all available lead times for the specified init_time(s).
Requesting longer time periods results in higher costs. Only request the time period you are interested in to save costs. Check Pricing for more details on how the costs are computed.
time (optional)
time (optional)Filter by absolute forecast valid time.
Single datetime:
{
"time": "2025-10-22T12:00:00Z"
}List of datetimes:
{
"time": [
"2025-10-22T00:00:00Z",
"2025-10-22T06:00:00Z",
"2025-10-22T12:00:00Z"
]
}Time range:
{
"time": {
"start": "2025-10-22T00:00:00Z",
"end": "2025-10-23T00:00:00Z"
}
}timedelta_unit (optional)
timedelta_unit (optional)Units for prediction_timedelta and latest_min_prediction_timedelta.
Type: string
Default: "h" (hours)
Supported values:
"m","minute","minutes"- Minutes"h","hour","hours"- Hours"d","day","days"- Days
Example:
{
"prediction_timedelta": {
"start": 0,
"end": 7
},
"timedelta_unit": "d" // 0 to 7 days
}latest_min_prediction_timedelta (optional)
latest_min_prediction_timedelta (optional)When using init_time: "latest", only include forecasts with at least this much lead time available.
Example:
{
"init_time": "latest",
"latest_min_prediction_timedelta": 24,
"timedelta_unit": "h" // Only use latest forecast if it has ≥24h lead time
}This is useful when you need a minimum forecast horizon regardless of when the forecast was generated.
Variable Selection
variables (optional)
variables (optional)Weather variables to retrieve.
Type: array of strings
Default: All variables available for the selected models
{
"variables": [
"air_temperature_at_height_level_2m",
"wind_speed_at_height_level_100m",
"precipitation_amount_sum_1h"
]
}Common variables:
air_temperature_at_height_level_2m- Temperature at 2m (Kelvin)wind_speed_at_height_level_10m- Wind speed at 10m (m/s)wind_speed_at_height_level_100m- Wind speed at 100m (m/s)relative_humidity_at_height_level_2m- Relative humidity (%)precipitation_amount_sum_1h- 1-hour accumulated precipitation (mm)surface_downwelling_shortwave_flux_sum_1h- Solar radiation (W/m²)air_pressure_at_mean_sea_level- Sea level pressure (Pa)And many more...
If omitted: Returns all variables common to the selected models.
Only request the variables you are interested in to save costs.
Some variables such as solar and wind at 100m are only available in a Pro subscription or higher.
Check Pricing for more information.
Aggregation and Grouping
Aggregate data across space and/or time using group_by and aggregation.
Concept
group_by: Dimensions to preserve in the resultaggregation: How to aggregate values within each group
group_by (optional)
group_by (optional)Dimensions to group by.
Type: array of strings
Supported dimensions:
"model"- Model identifier"init_time"- Forecast initialization time"time"- Forecast valid time"prediction_timedelta"- Lead time"latitude"- Latitude coordinate"longitude"- Longitude coordinate"point"- Equivalent to settings bothlatitudeandlongitude. Useful for ensemble models where you are interested in statistics per location (e.g. min, max, std)"market_zone"- Market zone (when using geo type market_zone)"country_key"- Country (when using geo type country_key)
Time transformations:
"time__to_start_of(hour)"or"hourly"- Grouped by start of hour"time__to_start_of(day)"or"daily"- Grouped by start of day"time__to_start_of(week)"or"weekly"- Grouped by start of week"time__to_start_of(month)"or"monthly"- Grouped by start of month"time__to_start_of(year)"or"yearly"- Grouped by start of yearSame transformations available for
init_time
Example - Average over all grid points for each valid time:
{
"group_by": ["model", "init_time", "time"],
"aggregation": ["avg"]
}Example - Daily min & max:
{
"group_by": ["model", "daily"], // "daily" is shortcut for "time__to_start_of(day)"
"aggregation": ["min", "max"]
}aggregation (optional)
aggregation (optional)Aggregation functions to apply when grouping.
Type: array of objects or strings
Supported functions:
"avg"- Average"std"- Standard deviation"min"- Minimum"max"- Maximum"sum"- Sum"count"- Count"median"- Median"quantile"- Quantile (requires parameter)
Simple aggregation (all variables):
{
"aggregation": ["avg"]
}Multiple aggregations:
{
"aggregation": ["avg", "std", "min", "max"]
}Variable-specific aggregation:
{
"aggregation": [
{
"aggregation": "avg",
"variables": ["air_temperature_at_height_level_2m"]
},
{
"aggregation": "max",
"variables": ["wind_speed_at_height_level_100m"]
}
]
}Parameterized aggregation (quantile):
{
"aggregation": [
{
"aggregation": "quantile",
"parameters": [0.95], // 95th percentile
"variables": ["wind_speed_at_height_level_100m"]
}
]
}Short syntax for parameterized aggregation:
{
"aggregation": ["quantile_(0.95)__wind_speed_at_height_level_100m"]
}group_by is required when aggregation is set.
Common Aggregation Patterns
1. Spatial average at each timestep:
{
"geo": { "type": "market_zone", "value": "DE" },
"group_by": ["model", "init_time", "time"],
"aggregation": ["avg"]
}2. Daily maximum over a region:
{
"geo": {
"type": "bounding_box",
"value": [
[45.0, 5.0],
[50.0, 15.0]
]
},
"group_by": ["model", "daily"],
"aggregation": ["max"]
}3. Statistics at a specific location (for ensemble models, e.g. ept2_e):
{
"geo": { "type": "point", "value": [47.37, 8.54] },
"group_by": ["model", "latitude", "longitude", "time"],
"aggregation": ["avg", "std", "min", "max"]
}Weighting
Apply weighted aggregation based on capacity or population distribution.
weighting (optional)
weighting (optional)Type: object
Structure:
{
"type": "wind_capacity" | "solar_capacity" | "population"
}Weighting types:
wind_capacity- Weight by installed wind power capacity{ "weighting": { "type": "wind_capacity" } }solar_capacity- Weight by installed solar power capacity{ "weighting": { "type": "solar_capacity" } }population- Weight by population density{ "weighting": { "type": "population" } }
Example - Wind capacity-weighted average:
{
"models": ["ept2"],
"geo": { "type": "market_zone", "value": "DE" },
"init_time": "latest",
"variables": ["wind_speed_at_height_level_100m"],
"prediction_timedelta": { "start": 0, "end": 72 },
"weighting": { "type": "wind_capacity" },
"group_by": ["model", "init_time", "time"],
"aggregation": ["avg"]
}This computes the wind speed weighted by where wind turbines are located, giving more weight to regions with higher wind capacity and less weight to regions without production capacity.
Weighting only applies when using aggregation with "avg". Other aggregation functions ignore the weighting parameter.
Output Control
Control how results are formatted and returned.
include_time (optional)
include_time (optional)Include the forecast valid time column in results.
Type: boolean
Default: false
{
"include_time": true
}Result with include_time: true:
{
"model": ["ept2", "ept2"],
"init_time": ["2025-10-22T00:00:00Z", "2025-10-22T00:00:00Z"],
"prediction_timedelta": [1, 2],
"time": ["2025-10-22T01:00:00Z", "2025-10-22T02:00:00Z"],
"air_temperature_at_height_level_2m": [285.3, 284.8]
}time_zone (optional)
time_zone (optional)IANA time zone for time formatting.
Type: string
Default: "UTC"
{
"time_zone": "Europe/Berlin"
}Common time zones:
"UTC"(default)"Europe/Berlin""America/New_York""America/Los_Angeles""Asia/Tokyo"
All time in responses are formatted in the specified time zone.
The time zone is not applied to init_time, which is always in UTC
order_by (optional)
order_by (optional)Sort results by specific dimensions.
Type: array of strings
{
"order_by": ["model", "init_time", "prediction_timedelta"]
}Sortable dimensions:
"model""init_time""time""prediction_timedelta""latitude""longitude"Any variable name (e.g.,
"air_temperature_at_height_level_2m")
Constraints:
When using
group_by,order_bydimensions must be in thegroup_bylistRequired when using
pagination
pagination (optional)
pagination (optional)Limit the number of results returned.
Type: object
Structure:
{
"limit": 1000, // Max rows to return
"offset": 0 // Number of rows to skip
}Example - Get first 1000 rows:
{
"order_by": ["model", "init_time", "time"],
"pagination": {
"limit": 1000,
"offset": 0
}
}Example - Get next 1000 rows:
{
"order_by": ["model", "init_time", "time"],
"pagination": {
"limit": 1000,
"offset": 1000
}
}order_by is required when using pagination.
Best Practices
Performance
Use Arrow format for large queries: Set
?format=arrowin the URL for queries returning >10k rowsEnable streaming for very large queries: Add
&stream=truewith Arrow format for >100k rowsLimit prediction_timedelta range: Request only the lead times you need
Split historical data into multiple requests: Fetch data in chunks
Cost Optimization
Estimate costs first: Use
POST /v1/forecast/costbefore running expensive queriesSet
request_credit_limit: Prevents accidentally expensive queries (default: 50 credits)Aggregate when possible: Grouped queries accessing many points cost less per point
Select specific variables: Don't request all variables if you only need a few
Query Construction
Use
group_byfor spatial aggregates: When querying regions, group by time dimensionsInclude
timein results: Setinclude_time: truefor easier result interpretationSpecify time zones: Use
time_zoneto get times in your local timezoneOrder results: Use
order_byfor predictable result orderingTest with small queries first: Start with short time ranges, then expand
Validation and Errors
The API validates all query parameters and returns helpful error messages.
Common validation errors:
Invalid model: Model not available or not in your subscription
{ "detail": "Model 'xyz' is not valid" }Variable not supported: Variable not available for selected model(s)
{ "detail": "Variables ['xyz'] are not supported by all models" }Invalid geo filter: Geographic coordinates out of range
{ "detail": "Latitude must be between -90 and 90" }Missing required parameter: Required field not provided
{ "detail": "Field required: 'models'" }Insufficient credits: Not enough credits for the query
{ "detail": "Insufficient credits. Available: 10.5. Required: 25.3" }Response too large: Query returns too many rows
{ "detail": "Query exceeds maximum rows for JSON format (50000). Use format=arrow or add pagination." }
Next Steps
See examples: Check out the collection of example queries
Explore variables: Use
GET /v1/forecast/metato see all available models and variablesCheck availability: Use
GET /v1/forecast/available-forecaststo see available forecast timesCheck the OpenAPI docs: Endpoint & data models ready for you to try out
Last updated