Query Structure

Overview

A forecast query to the data endpoint is a JSON object that specifies:

  1. What data you want (models, variables)

  2. Where you want it (geographic filter)

  3. When you want it (temporal filter)

  4. How to process it (aggregation, grouping, weighting)

  5. 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)

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.

Use GET /v1/forecast/meta to fetch available models and supported variables programmatically


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)

Forecast initialization time(s).

Type: string | array | object

Options:

  1. Latest forecast:

    { "init_time": "latest" }
  2. Specific datetime (ISO 8601 format):

    { "init_time": "2025-10-22T00:00:00Z" }
    { "init_time": "2025-10-22 00:00:00" }
  3. List of datetimes:

    {
      "init_time": ["2025-10-22T00:00:00Z", "2025-10-22T12:00:00Z"]
    }
  4. 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).


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


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"]
}

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"]
}

Temporal Filtering

Control which forecast times to retrieve using temporal filters.

Forecast Time Concepts

  • init_time (required): When the forecast was generated

  • prediction_timedelta: How far ahead from init_time (lead time)

  • time: Absolute forecast valid time (init_time + prediction_timedelta)

prediction_timedelta (optional)

Forecast lead time from initialization.

Units: Controlled by timedelta_unit parameter (default: "h" for hours)

The timedelta_unit also affects the unit in which prediction_timedelta is returned

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).


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"
  }
}

time and prediction_timedelta are complementary ways to filter temporal data. You can use either or both.

You can use time with multiple init_time to compare how the forecast for a given point in time has changed.


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)

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)

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...

You can always query /v1/forecasts/meta to check the available variables per model.

If omitted: Returns all variables common to the selected models.


Aggregation and Grouping

Aggregate data across space and/or time using group_by and aggregation.

Concept

  • group_by: Dimensions to preserve in the result

  • aggregation: How to aggregate values within each group

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 both latitude and longitude . 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 year

  • Same transformations available for init_time

You can use time transformations to get for example the daily min & max temperature for a given location. Make sure to set time_zone to get the data group by the start-of-day in the region you are interested in.

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 is required when using group_by. Per default avg is used.


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"]
}

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)

Type: object

Structure:

{
  "type": "wind_capacity" | "solar_capacity" | "population"
}

Weighting types:

  1. wind_capacity - Weight by installed wind power capacity

    {
      "weighting": { "type": "wind_capacity" }
    }
  2. solar_capacity - Weight by installed solar power capacity

    {
      "weighting": { "type": "solar_capacity" }
    }
  3. 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.


Output Control

Control how results are formatted and returned.

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]
}

Automatically set to true if time is in group_by or order_by.


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.


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_by dimensions must be in the group_by list

  • Required when using pagination


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
  }
}

Best Practices

Performance

  1. Use Arrow format for large queries: Set ?format=arrow in the URL for queries returning >10k rows

  2. Enable streaming for very large queries: Add &stream=true with Arrow format for >100k rows

  3. Limit prediction_timedelta range: Request only the lead times you need

  4. Split historical data into multiple requests: Fetch data in chunks

Cost Optimization

  1. Estimate costs first: Use POST /v1/forecast/cost before running expensive queries

  2. Set request_credit_limit: Prevents accidentally expensive queries (default: 50 credits)

  3. Aggregate when possible: Grouped queries accessing many points cost less per point

  4. Select specific variables: Don't request all variables if you only need a few

Query Construction

  1. Use group_by for spatial aggregates: When querying regions, group by time dimensions

  2. Include time in results: Set include_time: true for easier result interpretation

  3. Specify time zones: Use time_zone to get times in your local timezone

  4. Order results: Use order_by for predictable result ordering

  5. Test 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/meta to see all available models and variables

  • Check availability: Use GET /v1/forecast/available-forecasts to see available forecast times

  • Check the OpenAPI docs: Endpoint & data models ready for you to try out

Last updated