Skip to content

Enverus NRF API Client

Client for the Enverus Nodal Resource Forecast (NRF) API, providing wind and solar power forecasts, extreme weather flags, and related data for US ISOs.

Authentication

The client requires an Enverus user ID, read from the ENVERUS_USERID environment variable or passed directly to the constructor. No separate API key or token is needed.

Core Workflow

from progridpy.utils.enverus_api import EnverusClient, EnverusDataType, WeatherModel

client = EnverusClient()
df = client.fetch_df(
    "miso",
    EnverusDataType.WIND_FORECAST,
    "20260113",                   # Operating Day
    model=WeatherModel.HRRR,
    gen_hr=12,                    # 12:00 UTC model run (default for HRRR)
)

The client automatically requests the model run from OD-1 and filters the response to return only the requested operating day's 24-hour forecast.

Supported Data Types

Enum Member Description
WIND_FORECAST Wind power forecast
SOLAR_FORECAST Solar power forecast
EXTREME_WIND_SPEED High wind speed flags (HRRR only)
TURBINE_ICING Turbine icing flags (HRRR only)
SOLAR_SMOKE Solar smoke flags (HRRR only)
SOLAR_SNOW Solar snow cover flags (HRRR only)

Weather Models

Model Default gen_hr Availability
HRRR 12 (12:00 UTC) Hourly updates; data from 2020-12-03
ECMWF 0 (00:00 UTC) Longer forecast horizon; data from 2018-01-01

Enverus NRF API client for renewable energy forecasts.

Classes

EnverusClient

EnverusClient(userid: str | None = None, timeout: float = 120.0)

Client for the Enverus NRF API.

Fetches wind/solar forecast data and extreme weather flags. For a date range, fetches the forecast generated on each day and extracts that day's data, providing the most accurate forecast for each day.

Args: userid: Enverus API user ID. Reads from ENVERUS_USERID env var if not provided. timeout: Request timeout in seconds.

Example: >>> client = EnverusClient(userid="your-userid") >>> df = client.fetch_df("miso", EnverusDataType.WIND_FORECAST, "20250101", "20250103")

Source code in src/progridpy/utils/enverus_api/client.py
def __init__(self, userid: str | None = None, timeout: float = 120.0) -> None:
    self.userid = userid or os.getenv("ENVERUS_USERID")
    if not self.userid:
        raise EnverusAuthError("Enverus userid required. Set ENVERUS_USERID env var or pass to constructor.")
    self.timeout = timeout

Functions

fetch_df
fetch_df(iso: str, feature: EnverusDataType, start_date: str | None = None, end_date: str | None = None, model: WeatherModel | None = None, gen_hr: int | None = None) -> DataFrame

Fetch forecast data for a date range (day-ahead style).

For each date in the range, fetches the forecast from the previous day's model run and extracts only that day's forecast data. This is designed for day-ahead trading where you need the full 24-hour forecast for the Operating Day (OD) using the latest model available on OD-1.

The HRRR 12:00 UTC model run produces ~48 hours of forecasts, so the OD-1 model run contains full 24-hour coverage for OD (00:00 to 23:59).

Args: iso: ISO identifier ('miso', 'ercot', 'spp', 'pjm', 'nyiso', 'isone', 'caiso'). feature: Feature type to fetch. start_date: Start date (YYYYMMDD), inclusive. This is the Operating Day you want forecasts for. Defaults to today. end_date: End date (YYYYMMDD), inclusive. Defaults to start_date. model: Weather model (ECMWF or HRRR). Defaults to feature's default model. gen_hr: Model generation hour (UTC). Defaults to 12 for HRRR, 0 for ECMWF. The 12 UTC run is recommended as it's the most recent forecast for trading.

Returns: DataFrame with columns: [interval_start_local, node, ]

Raises: ValueError: If model is not supported for the feature.

Example: # On Jan 12 (OD-1), fetch forecast for Jan 13 (OD) >>> df = client.fetch_df("miso", EnverusDataType.WIND_FORECAST, "20260113") # Returns full 24-hour forecast for Jan 13 (00:00 to 23:59 local time)

Source code in src/progridpy/utils/enverus_api/client.py
def fetch_df(
    self,
    iso: str,
    feature: EnverusDataType,
    start_date: str | None = None,
    end_date: str | None = None,
    model: WeatherModel | None = None,
    gen_hr: int | None = None,
) -> pd.DataFrame:
    """Fetch forecast data for a date range (day-ahead style).

    For each date in the range, fetches the forecast from the previous day's
    model run and extracts only that day's forecast data. This is designed
    for day-ahead trading where you need the full 24-hour forecast for the
    Operating Day (OD) using the latest model available on OD-1.

    The HRRR 12:00 UTC model run produces ~48 hours of forecasts, so the
    OD-1 model run contains full 24-hour coverage for OD (00:00 to 23:59).

    Args:
        iso: ISO identifier ('miso', 'ercot', 'spp', 'pjm', 'nyiso', 'isone', 'caiso').
        feature: Feature type to fetch.
        start_date: Start date (YYYYMMDD), inclusive. This is the Operating Day
            you want forecasts for. Defaults to today.
        end_date: End date (YYYYMMDD), inclusive. Defaults to start_date.
        model: Weather model (ECMWF or HRRR). Defaults to feature's default model.
        gen_hr: Model generation hour (UTC). Defaults to 12 for HRRR, 0 for ECMWF.
            The 12 UTC run is recommended as it's the most recent forecast for trading.

    Returns:
        DataFrame with columns: [interval_start_local, node, <feature.value>]

    Raises:
        ValueError: If model is not supported for the feature.

    Example:
        # On Jan 12 (OD-1), fetch forecast for Jan 13 (OD)
        >>> df = client.fetch_df("miso", EnverusDataType.WIND_FORECAST, "20260113")
        # Returns full 24-hour forecast for Jan 13 (00:00 to 23:59 local time)
    """
    spec = FEATURE_SPECS[feature]
    model = model or spec["default_model"]

    if model not in spec["models"]:
        supported = ", ".join(m.value for m in spec["models"])
        raise ValueError(f"{feature.value} only supports: {supported}")

    today = datetime.now().strftime("%Y%m%d")
    start = start_date or today
    end = end_date or start

    start_dt = datetime.strptime(start, "%Y%m%d")
    end_dt = datetime.strptime(end, "%Y%m%d")

    dfs = []
    current = start_dt

    while current <= end_dt:
        # Request model run from previous day (OD-1) to get full coverage for OD
        model_run_date = current - timedelta(days=1)
        model_run_date_str = model_run_date.strftime("%Y%m%d")
        forecast_date_str = current.strftime("%Y%m%d")

        logger.trace(
            f"Fetching {feature.value} ({model.value}) for {iso}: "
            f"model_run={model_run_date_str}, forecast_date={forecast_date_str}"
        )

        payload = self._fetch_single_day(iso, feature, model, model_run_date_str, gen_hr)
        df = reshape_response(payload, feature)

        if not df.empty:
            # Filter to only include the requested forecast date (OD)
            day_start = current.replace(hour=0, minute=0, second=0, microsecond=0)
            day_end = day_start + timedelta(days=1)
            df_filtered = df[(df["interval_start_local"] >= day_start) & (df["interval_start_local"] < day_end)]
            dfs.append(df_filtered)

        current += timedelta(days=1)

    if not dfs:
        return pd.DataFrame(columns=["interval_start_local", "node", feature.value])

    return pd.concat(dfs, ignore_index=True).sort_values(["interval_start_local", "node"]).reset_index(drop=True)

EnverusAPIError

EnverusAPIError(message: str, status_code: int | None = None, response_body: str | None = None)

Bases: Exception

Raised when the Enverus API returns an error or is unreachable.

Source code in src/progridpy/utils/enverus_api/exceptions.py
def __init__(self, message: str, status_code: int | None = None, response_body: str | None = None):
    self.status_code = status_code
    self.response_body = response_body
    super().__init__(message)

EnverusAuthError

EnverusAuthError(message: str, status_code: int | None = None, response_body: str | None = None)

Bases: EnverusAPIError

Raised when authentication fails (missing or invalid userid).

Source code in src/progridpy/utils/enverus_api/exceptions.py
def __init__(self, message: str, status_code: int | None = None, response_body: str | None = None):
    self.status_code = status_code
    self.response_body = response_body
    super().__init__(message)

EnverusShapeError

Bases: Exception

Raised when API response has unexpected structure or missing required fields.

EnverusDataType

Bases: Enum

Supported Enverus NRF feature types.

WeatherModel

Bases: Enum

Weather models available for forecasts.