Language

Home Assistant · electricity price and CO2

Two equally supported ways to get price and CO2 data into Home Assistant

There are two fully supported ways to bring Energy Price Forecast EU into Home Assistant: a native HACS integration with UI-based setup, or a classic YAML package with a REST sensor. Both use the same API and deliver current values, live states and the most important time windows.

Which path fits you?

Both paths call the same summary endpoint and return the same data. The difference lies in setup and maintenance.

Open-source repository of the HACS integration: github.com/BackupBaTTerY/energypriceforecast-home-assistant. Both paths can also run side by side, as long as you do not create identical entity IDs twice.

Start free, plan further when needed

The Home Assistant setup stays usable without an API key and typically delivers up to 48 hours. If you want to plan larger loads across several days, you can try Private Pro with up to 120 hours free for 14 days.

Important API note

New setups should use https://api.energypriceforecast.eu/api/v1/... as their address. The summary API does not return a complete price series but a compact automation block with the current price, the current CO2 value, the best window and meta information about your access.

How to set up the HACS integration

The integration is listed in the HACS default store, so there is nothing to add by hand.

  1. In HACS, search for “Energy Price Forecast EU” and download it, then restart Home Assistant.
  2. Settings → Devices & services → Add integration, search for “Energy Price Forecast EU” and choose your market and any optional settings.

The button above (“Add the HACS integration”) opens it directly in HACS if HACS is already installed.

Compare the forecast with published prices

Green shows published day-ahead prices, orange the current forecast and the dashed blue line the first saved forecast. When official prices arrive, the earlier prediction remains visible for comparison.

  1. Update Energy Price Forecast EU in HACS to at least 1.12.0 and restart Home Assistant. Also install ApexCharts Card 2.1.0 or newer through HACS.
  2. Replace all three sensor.CHANGE_ME placeholders with the same price-series entity from Developer Tools → States. For your configured all-in price, use the Current retail price entity instead. Paste the YAML into Dashboard → Edit → Add card → Manual.

Collection starts with this update and survives restarts. Snapshots remain until the target market day ends; earlier predictions cannot be reconstructed. captured_at is the Home Assistant observation time, not the model run time. This is not a fixed-lead-time quality measurement or an unlimited archive.

Match the axis label to the sensor unit, e.g. EUR/kWh rather than ct/kWh. A calculated retail price is not a guaranteed bill. Anonymous API access remains up to 48 hours; for up to 120 hours, use the appropriate access and graph_span: 6d. This card is for the HACS integration, not the REST-summary example below, and adds no API requests.

Ready-to-paste YAML
type: custom:apexcharts-card
header:
  show: true
  title: Energy Price Forecast EU
graph_span: 3d
span:
  start: day
now:
  show: true
  label: Now
yaxis:
  - id: price
    decimals: 3
    apex_config:
      title:
        text: EUR/kWh
all_series_config:
  yaxis_id: price
  type: line
  curve: stepline
  extend_to: false
  stroke_width: 2
  float_precision: 4
  show:
    legend_value: false
series:
  # Replace ALL three placeholders with the SAME price or retail sensor.
  - entity: sensor.CHANGE_ME
    name: Known (day-ahead)
    color: "#43a047"
    data_generator: |
      const raw = [...(entity.attributes.raw_today ?? []),
                   ...(entity.attributes.raw_tomorrow ?? [])];
      const slots = raw.map(e => ({
        start: Date.parse(e.start), end: Date.parse(e.end), value: e.value
      })).filter(e => Number.isFinite(e.start) && Number.isFinite(e.end)
        && e.end > e.start && typeof e.value === 'number' && Number.isFinite(e.value))
        .sort((a, b) => a.start - b.start);
      const points = [];
      let previousEnd = null;
      for (const slot of slots) {
        if (previousEnd !== null && slot.start > previousEnd) {
          points.push([previousEnd, null], [slot.start - 1, null]);
        }
        points.push([slot.start, slot.value], [slot.end - 1, slot.value]);
        previousEnd = slot.end;
      }
      return points;
  - entity: sensor.CHANGE_ME
    name: Current forecast
    color: "#fb8c00"
    data_generator: |
      const raw = entity.attributes.raw_forecast ?? [];
      const slots = raw.map(e => ({
        start: Date.parse(e.start), end: Date.parse(e.end), value: e.value
      })).filter(e => Number.isFinite(e.start) && Number.isFinite(e.end)
        && e.end > e.start && typeof e.value === 'number' && Number.isFinite(e.value))
        .sort((a, b) => a.start - b.start);
      const points = [];
      let previousEnd = null;
      for (const slot of slots) {
        if (previousEnd !== null && slot.start > previousEnd) {
          points.push([previousEnd, null], [slot.start - 1, null]);
        }
        points.push([slot.start, slot.value], [slot.end - 1, slot.value]);
        previousEnd = slot.end;
      }
      return points;
  - entity: sensor.CHANGE_ME
    name: Saved forecast (first seen)
    color: "#1e88e5"
    stroke_dash: 5
    data_generator: |
      const raw = entity.attributes.raw_forecast_reference ?? [];
      const slots = raw.map(e => ({
        start: Date.parse(e.start), end: Date.parse(e.end), value: e.value
      })).filter(e => Number.isFinite(e.start) && Number.isFinite(e.end)
        && e.end > e.start && typeof e.value === 'number' && Number.isFinite(e.value))
        .sort((a, b) => a.start - b.start);
      const points = [];
      let previousEnd = null;
      for (const slot of slots) {
        if (previousEnd !== null && slot.start > previousEnd) {
          points.push([previousEnd, null], [slot.start - 1, null]);
        }
        points.push([slot.start, slot.value], [slot.end - 1, slot.value]);
        previousEnd = slot.end;
      }
      return points;
Optional: tailor the card with AI
Help me tailor an ApexCharts card for Energy Price Forecast EU. First ask for my exact entity ID, sensor unit, integration version, hourly/quarter-hourly settlement and whether I want market or retail prices. Do not ask for an API key.
From integration 1.12.0, Price forecast series and Current retail price expose:
- raw_today and raw_tomorrow: published day-ahead slots.
- raw_forecast: current forecast slots beyond official coverage.
These lists contain {start, end, value}.
- raw_forecast_reference: {start, end, value, captured_at}; the first forecast received for a then-future slot, saved locally. Later forecasts and official prices never overwrite it.
References accumulate only after updating, survive restarts and are removed after the target market day ends. captured_at is the HA observation time, not model creation.
Use green for official prices, orange for the current forecast and dashed blue for the saved forecast, on the same axis in the sensor's unit. Require ApexCharts Card 2.1.0 or newer. Use each series' data_generator and extend_to: false. Draw start through end, preserve gaps with null, and retain genuine zero/negative prices. Never copy official values into a forecast line. Use only the documented attributes.
Optionally ask about the hourly/weekend plan: its hours attribute contains {start, end, average_value}. Mark those times separately without changing the plan. Return complete YAML for a manual Lovelace card and explain where to paste it. A chart alone does not switch a charger.

Up and running in 5 minutes (YAML package)

If you chose the YAML path, take these three files. The package file fetches the data, the Lovelace card displays it and the example automation shows the actual use.

Deliberately simpleThe package file only uses the summary endpoint. No HACS, no add-on, no Python script needed.
Built for real controlWhat matters here is not only start times but also states such as is_cheapest_window_now and the remaining minutes.
Server-friendly pollingThe example polls every 30 minutes by default. For day-ahead plus forecast that is normally entirely sufficient.

What the result looks like

Preview of a Lovelace card with the current price, CO2 value and price windows.
The Lovelace card shows the entities from the package file directly, without you having to build your own dashboards first.
Preview of a simple Home Assistant automation triggered by the cheapest price window.
The example automation deliberately starts simple and can then be adapted to a wallbox, heat pump or switch.

How to add the package

  1. Download the package file and save it as /config/packages/energypriceforecast.yaml.
  2. If packages are not enabled yet, extend configuration.yaml with this block.
  3. Reload Home Assistant or restart it once.
  4. Then import the Lovelace card and, optionally, the example automation.
homeassistant:
  packages: !include_dir_named packages

You can add the Lovelace card to a dashboard as a Manual card. You can import the automation as YAML under Automations as well, or simply use it as a template.

Which entities do you get afterwards?

The package file creates one raw source and, built on it, entities you can use directly. That shows at once whether the API runs cleanly, and you can build your own automations without JSON parsing.

Entity Meaning Typical use
sensor.energypriceforecast_apiRaw response of the summary endpoint including flat, price, co2 and meta.Debugging and the basis for further templates.
sensor.epf_current_priceCurrent price in the running slot. The source can be day-ahead or forecast.Price-controlled wallbox, heat pump, boiler.
sensor.epf_current_co2_intensityCurrent CO2 value in the running slot.CO2-oriented load shifting.
binary_sensor.epf_cheapest_price_window_activeon, when the calculated best price window is already running.A direct run decision without time comparisons of your own.
sensor.epf_cheapest_price_window_remaining_minutesRemaining duration of the currently best price window.Estimating how long a load may keep running.
sensor.epf_next_full_price_windowThe next complete future window for price.Starting later or planning ahead.
sensor.epf_api_key_statusStatus of the stored API key.Seeing quickly whether 48 h or 120 h really apply.
sensor.epf_allowed_horizonHorizon in hours allowed on the server side.Checking whether your setup really gets the desired length.

Which period does the API deliver exactly?

For Home Assistant, what matters is not only the URL but also the logic behind it. That is exactly what should be transparent.

Without an API keyThe example uses hours=48. That gives you the simple test horizon that is meant to be public without an additional key.
With an API keyOnce you set the bearer token in the header, you can raise the horizon in the package to up to hours=120 or 5 days.
Price resolutionDay-ahead can come in 15-minute slots. The forecast beyond that stays hourly. The summary combines both cleanly.
Day-ahead firstAs soon as official day-ahead prices are available, they take precedence. Forecast values are used only for the remaining open period.
CO2 stays hourlyThe CO2 data is not artificially converted to quarter hours. For the summary, the hourly CO2 view is usually sufficient.
Ready-computed windowsThe API delivers not only raw values but also the cheapest window, the greenest window and the next complete future window.

Where do you enter the API key and how do you extend the horizon?

Both happen directly in the package file. You do not need a second endpoint; you only activate the Authorization header and adjust the hours value.

rest:
  - resource: "https://api.energypriceforecast.eu/api/v1/home-assistant/summary?country=de&hours=120&window_hours=4"
    scan_interval: 1800
    headers:
      Accept: "application/json"
      Authorization: "Bearer YOUR_API_KEY"

Whether your key is really being used then shows in sensor.epf_api_key_status, sensor.epf_allowed_horizon and sensor.epf_used_horizon.

What does the summary endpoint give you in practice?

The summary path is deliberately built for automations. It saves you a lot of logic of your own, because you do not have to parse long hourly series first to make simple decisions.

Field Meaning Why it is useful
flat.current_priceCurrent price value in the running slot.Direct current state for price logic.
flat.current_co2_g_kwhCurrent CO2 value in the running slot.Direct current state for CO2 logic.
flat.is_cheapest_window_nowShows whether the best price window is already active.More important than a start time alone.
flat.cheapest_window_remaining_minutesRemaining duration of the running best price window.Useful for runtime decisions.
price.next_full_window.startNext complete future window for price.Planning when you deliberately want to start later.
meta.api_key_stateStatus of the stored key.Troubleshooting and test phase.
meta.allowed_horizon_hoursWhat is allowed on the server side for this access.Clear transparency about limits.
meta.used_horizon_hoursWhat the API actually delivered.This page and the backend stay honestly in sync.

Official field reference for home-assistant/summary

The summary endpoint is intentionally compact. For many automations, the flat block is already enough. If you want more control or custom dashboards, use the more structured price, co2, combined and source blocks as well.

Field Meaning Typical use
flat.current_priceCurrent price in EUR/kWh. For the current slot it automatically uses the best available public source.Simple price sensor for automations.
flat.current_price_sourceSource label of the current price, for example day_ahead.Check whether the value is already official or still forecast-based.
flat.current_co2_g_kwhCurrent CO2 intensity in gCO2/kWh.CO2-based shifting of flexible loads.
flat.cheapest_window_*Start, end and average value of the cheapest time window in the requested horizon.EV charging, boilers, heat pumps.
flat.greenest_window_*Start, end and average value of the lowest-CO2 time window.Low-CO2 scheduling.
flat.combined_window_*Compromise window between price and CO2.When both matter and you do not want to define your own weighting.
price.currentObject with start, end, value, unit and source for the current price slot.Dashboards and more precise state logic.
price.cheapest_window_next_horizonStructured cheapest-window block including duration and source counts.When you want start, end and context together.
co2.currentObject with the current CO2 value and its time window.CO2 display without parsing flat.
co2.greenest_window_next_horizonStructured greenest-window block.Low-CO2 charging or heating logic.
combined.best_window_next_horizonCombined recommendation path with price, CO2 and score values.A single "best now" decision without building your own score.
source.priceMetadata about the price source, including Firestore document, day-ahead provider and counts of available entries.Debugging, monitoring and quality checks.
source.co2Metadata about the CO2 source.Debugging and traceability.
Important about price fieldsThe summary endpoint does not return the full raw series of every price variant. For Home Assistant it mainly returns the current state and already computed windows. For full hourly series, hourly-forecast is the better endpoint.
Current does not mean historical actualflat.current_price is the currently usable public price for the running slot. It can come from the official day-ahead auction or from another released source when the full market result is not available yet.
Retail only for selected marketsThe public retail-forecast endpoint is currently intended only for selected markets. For other markets, the summary deliberately stays on market or base values.

Example: charge the car only in the cheap window

That is exactly what the example automation is for. It deliberately uses the boolean binary_sensor.epf_cheapest_price_window_active instead of only triggering on a clock time. That is more robust, because the best window may already be running.

trigger:
  - platform: state
    entity_id: binary_sensor.epf_cheapest_price_window_active
    to: "on"

action:
  - service: persistent_notification.create
    data:
      title: "Cheapest price window active"

If you want to start a wallbox or a switch directly instead of a notification, you only replace the service in the last step with your own target service such as switch.turn_on.

Adapt it to your setup with AI

This prompt helps an AI build a suitable Home Assistant automation from the summary endpoint. It does not replace the simple copy-and-paste start above; it is meant for individual loads, deadlines and safety rules.

Protect your API key

Never give an AI your real API key. Always keep YOUR_API_KEY in the result and enter the key only locally in Home Assistant.

Show the prompt to copy
Help me build a safe Home Assistant automation using the Energy Price Forecast EU API.

Read the API contract first:
https://energypriceforecast.eu/openapi/integration-api.json

Use /api/v1/home-assistant/summary and do not invent fields. The API uses official day-ahead prices wherever available and forecast values only for the remaining open horizon.

Before writing YAML, ask me no more than these questions, one at a time:
1. Country or bidding zone?
2. Which load should be controlled and which Home Assistant entity switches it?
3. Power, required runtime and latest completion time?
4. Optimise for price, CO2 or both?
5. Free without a key and up to 48 hours, or Private Pro with up to 120 hours?
6. Do I use packages or configuration.yaml?

Rules for your result:
- Free: hours=48 and no Authorization header.
- Private Pro: hours=120 and header Authorization: Bearer YOUR_API_KEY.
- Never ask me to paste the real key into the chat.
- Handle HTTP errors and check meta.api_key_state and meta.allowed_horizon_hours.
- Briefly explain every API property you use.
- Produce complete, correctly indented YAML and state exactly where it belongs.
- Start with a safe test mode using a notification or input_boolean. Put real load switching in a separate, clearly marked step.
- Define safe behaviour for stale, missing or implausible data. An API error must never switch the load on unexpectedly.
- Finish with a checklist: HTTP 200, sensors populated, allowed horizon correct and test mode triggered correctly.

Ask when information is missing. Do not silently assume entity names, tariffs or electrical limits.

Machine-readable basis: OpenAPI contract of the integration endpoints.

When is summary enough, and when do you need more?

summary is enough for most Home Assistant automations

If you need current values, a best window and simple states, the summary endpoint is the right way. That is exactly what the package file is built for.

For your own price series or charts, a raw-series endpoint is better

If you later deliberately want to build your own scoring logic, charts or detailed time series, the summary endpoint is intentionally not as detailed as it could be. A forecast endpoint with the raw series is then the more suitable path.

Are these exact household electricity prices?

Not automatically. The summary works primarily with market or base prices. Your exact household price additionally depends on grid fees, taxes, levies and supplier surcharges.

What to do if no values appear after pasting?

Typical causes: the packages folder is not included, an indentation error in the YAML, Home Assistant was not reloaded, or a wrong value for country. The Home Assistant logs and the developer tools show this fastest.

Which zones get published day-ahead prices?

We only pass on published day-ahead prices where the source allows it. For NO1, NO3, NO4, NO5, SE1, SE2, FI, RO, SK, GR, BG and the Italian zones except North the endpoints therefore return our forecast for every hour since 24 October 2026, and the sensors mark those hours as forecast. You get the published prices there through Home Assistant's Nord Pool integration; for SK, GR, BG and Italy through an ENTSO-E integration with your own token.

Related: evcc with electricity price and CO2 forecast.

More on this