stroomprijzenapi Home Assistant

Home Assistant

Dutch electricity prices with the energy tax and VAT already on them, the price you actually pay, as sensors in Home Assistant. Four steps, ten minutes.

1. Get an API key

Fill in your email address on the front page and the key appears on screen. No account, no waiting, free. It gives you 50,000 requests a day; a Home Assistant setup needs two or three.

Copy it now: only a hash of it is stored here, so a key can be replaced but never shown again.

2. Put the key in secrets.yaml

secrets.yaml sits next to configuration.yaml and is the file that stays out of your screenshots, forum posts and git history.

# secrets.yaml
stroomprijzen_api_key: sp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

3. Send it with every request

The key travels as a query parameter called apiKey, in a params: block. Home Assistant builds the query string itself, so the URL in resource: stays clean:

params:
  apiKey: !secret stroomprijzen_api_key

Three things worth knowing about that block. It applies to the one rest: entry it sits in, so every entry needs its own. Anything else that belongs in the query goes in there too, such as date and resolution, because a ? in resource: and a params: block are not merged. And a key that is unknown or revoked is served as if it were not there, with an x-api-key-status header, rather than as a 401: your sensors keep working, but a typo is invisible until you look.

4. Your first sensor

Paste this into configuration.yaml, restart Home Assistant, and you have the current price:

rest:
  - resource: https://stroomprijzenapi.nl/api/v1/prices/now
    params:
      apiKey: !secret stroomprijzen_api_key
    scan_interval: 900
    sensor:
      - name: "Electricity price now"
        unique_id: stroomprijs_now
        value_template: "{{ value_json.point.allInEurKwh }}"
        unit_of_measurement: "EUR/kWh"
        state_class: measurement
        json_attributes_path: "$.point"
        json_attributes:
          - localTime
          - rawEurMwh
          - energyTaxEurKwh
          - odeEurKwh
          - vatRate

Check it under Developer tools → States: sensor.electricity_price_now should hold something like 0.3159, and its attributes the breakdown that adds up to it.

Use allInEurKwh, not rawEurKwh. The raw field is the bare market price: in 2026 it sits about 0.11 EUR/kWh below what a household pays, because energy tax and VAT are not in it. The gap is nearly constant, so the wrong field looks plausible rather than broken, which is why it gets shipped by accident.

Set Home Assistant's time zone to Europe/Amsterdam. A day here is a Dutch calendar day, and now() returns your instance's local time. On UTC, everything below is off by an hour or two.

5. More examples

Same shape as above, each with its own key. Open what you need.

Today and tomorrow, as two sensors

For charts and for automations that plan ahead you want whole days. The date is templated because the endpoint defaults to tomorrow once tomorrow has been published.

rest:
  - resource: https://stroomprijzenapi.nl/api/v1/prices
    params:
      date: "{{ now().strftime('%Y-%m-%d') }}"
      apiKey: !secret stroomprijzen_api_key
    scan_interval: 21600
    sensor:
      - name: "Electricity prices today"
        unique_id: stroomprijzen_today
        value_template: "{{ value_json.summary.averageEurKwh }}"
        unit_of_measurement: "EUR/kWh"
        state_class: measurement
        json_attributes:
          - date
          - count
          - summary
          - points

  - resource: https://stroomprijzenapi.nl/api/v1/prices
    params:
      date: "{{ (now() + timedelta(days=1)).strftime('%Y-%m-%d') }}"
      apiKey: !secret stroomprijzen_api_key
    scan_interval: 3600
    sensor:
      - name: "Electricity prices tomorrow"
        unique_id: stroomprijzen_tomorrow
        value_template: "{{ value_json.count }}"
        json_attributes:
          - date
          - count
          - summary
          - points

Tomorrow's prices appear around 14:00 CE(S)T. Before then the endpoint returns count: 0 and an empty points array. That is a normal 200 rather than an error, so guard your templates for it.

Add resolution: quarterhour to params: for quarter-hourly prices. The Dutch market has traded on quarter-hours since 1 October 2025; an hourly price is the mean of its four quarters.

Cheapest hour, and "is it cheap right now"

These need the today sensor above. Adjust the thresholds to your own habits.

template:
  - sensor:
      - name: "Cheapest hour today"
        unique_id: stroomprijs_cheapest_hour
        state: >-
          {% set p = state_attr('sensor.electricity_prices_today', 'points') %}
          {% if p %}
            {{ (p | sort(attribute='allInEurKwh') | first).localTime[11:16] }}
          {% else %}
            unknown
          {% endif %}

  - binary_sensor:
      - name: "Electricity is cheap now"
        unique_id: stroom_is_cheap
        state: >-
          {% set p = state_attr('sensor.electricity_prices_today', 'points') %}
          {% set now_price = states('sensor.electricity_price_now') | float(-1) %}
          {% if p and now_price >= 0 %}
            {% set cheapest = (p | map(attribute='allInEurKwh') | sort | list)[:6] %}
            {{ now_price <= cheapest[-1] }}
          {% else %}
            false
          {% endif %}

That binary sensor is on during the six cheapest hours of the day. Change the [:6] to whatever suits the appliance.

Running something when power is cheap
automation:
  - alias: "Charge the car when power is cheap"
    triggers:
      - trigger: state
        entity_id: binary_sensor.electricity_is_cheap_now
        to: "on"
    actions:
      - action: switch.turn_on
        target:
          entity_id: switch.car_charger

On Home Assistant older than 2024.10, write trigger:, platform:, action: and service: in place of the newer keys.

A chart of the day (ApexCharts)

The points attribute plugs straight into ApexCharts Card:

type: custom:apexcharts-card
graph_span: 24h
span:
  start: day
series:
  - entity: sensor.electricity_prices_today
    name: All-in price
    unit: EUR/kWh
    type: column
    data_generator: |
      return entity.attributes.points.map(p => [
        new Date(p.momentUtc).getTime(),
        p.allInEurKwh
      ]);

Use momentUtc for the timestamp, not localTime: Date handles the UTC instant unambiguously and the browser renders it in the viewer's own zone.

The Energy dashboard

Settings → Dashboards → Energy, then under your grid consumption source pick Use an entity with current price and select sensor.electricity_price_now.

Because that sensor is the all-in price, the costs the dashboard reports are what you pay per kWh, excluding the fixed parts of your bill that no per-kWh price can express. Standing charges, the annual tax rebate and your supplier's markup all sit outside this API.

6. Details, once it works

Fetching two or three times a day instead of on a timer

Day-ahead prices change twice in twenty-four hours: tomorrow's are published around 14:00 CE(S)T, and at midnight what was tomorrow becomes today. A published day never changes again, so a long scan_interval plus two automations beats any polling interval.

rest:
  - resource: https://stroomprijzenapi.nl/api/v1/prices
    params:
      date: "{{ now().strftime('%Y-%m-%d') }}"
      apiKey: !secret stroomprijzen_api_key
    scan_interval: 86400
    sensor:
      - name: "Electricity prices today"
        # ... as in section 5

automation:
  - alias: "Prices: today, after the date rolls over"
    triggers:
      - trigger: homeassistant
        event: start
      - trigger: time
        at: "00:05:00"
    actions:
      # Spread the load: without this every installation calls at 00:05:00 sharp.
      - delay: "{{ range(0, 600) | random }}"
      - action: homeassistant.update_entity
        target:
          entity_id: sensor.electricity_prices_today

  - alias: "Prices: tomorrow, until it lands"
    triggers:
      - trigger: time_pattern
        hours: "14"
        minutes: "/20"
      - trigger: time_pattern
        hours: "15"
        minutes: "/20"
    conditions:
      # Stop as soon as the sensor holds real data for the actual tomorrow.
      # Without the date check it would stop early: before the first afternoon
      # fetch the sensor still holds yesterday's idea of "tomorrow".
      - condition: template
        value_template: >-
          {% set want = (now() + timedelta(days=1)).strftime('%Y-%m-%d') %}
          {{ state_attr('sensor.electricity_prices_tomorrow', 'date') != want
             or (state_attr('sensor.electricity_prices_tomorrow', 'count') | int(0)) == 0 }}
    actions:
      - delay: "{{ range(0, 300) | random }}"
      - action: homeassistant.update_entity
        target:
          entity_id: sensor.electricity_prices_tomorrow

Two calls on a normal day, four when publication runs late. The random delays matter more than they look: fixed times mean every installation in the country arrives in the same second.

Deriving the current price without asking for it

Today's prices are already in Home Assistant, so the price this hour is a lookup rather than a request. This replaces the sensor from step 4: same entity name, same value, no requests at all.

template:
  - triggers:
      - trigger: time_pattern
        minutes: "/15"
      - trigger: state
        entity_id: sensor.electricity_prices_today
    sensor:
      - name: "Electricity price now"
        unique_id: stroomprijs_now
        unit_of_measurement: "EUR/kWh"
        state_class: measurement
        state: >-
          {% set points = state_attr('sensor.electricity_prices_today', 'points') or [] %}
          {% set hit = points | selectattr('localTime', 'match', now().strftime('%Y-%m-%dT%H')) | list %}
          {{ hit[0].allInEurKwh if hit else none }}

It matches on the local hour, so Home Assistant has to be on Europe/Amsterdam. On quarter-hourly data, match '%Y-%m-%dT%H:%M' against a quarter-hour boundary instead.

What a price point looks like
{
  "momentUtc":        "2026-08-14T22:00:00.000Z",
  "localTime":        "2026-08-15T00:00:00+02:00",
  "rawEurMwh":        169.45,
  "rawEurKwh":        0.16945,
  "energyTaxEurKwh":  0.09161,
  "odeEurKwh":        0,
  "vatRate":          0.21,
  "exclVatEurKwh":    0.26106,
  "allInEurKwh":      0.315883
}

A Dutch day has 23 or 25 entries on the clock-change days in March and October. Never assume 24, and never line two series up by array position; match on momentUtc.

Checking the tax numbers yourself

The tax layer is the part you should not have to take on faith. /api/v1/tax-rates publishes the rates and the formula, and /api/v1/test-vectors gives a worked amount for every tax year since 2019, with the sum written out:

(100.0 / 1000 + 0.09161 + 0) * (1 + 0.21) = 0.2318481

Those come from the official Belastingdienst tables, computed with exact decimal arithmetic rather than by running the API, so they check it rather than agree with it by construction.

When something looks wrong

The price is far lower than my energy bill

You are reading rawEurKwh. Use allInEurKwh.

The prices are shifted by an hour or two

Home Assistant is not on Europe/Amsterdam, or something is building its own UTC-midnight window. A Dutch day starts at 22:00Z in summer and 23:00Z in winter, so pass date and let the API work it out.

Tomorrow is empty

Normal before roughly 14:00 CE(S)T. Check /api/v1/status to see when data last landed.

My key shows no usage

Either it is not being sent or it is not recognised, and both look like working sensors, because a key we do not recognise is served anonymously. Ask the API what it thinks:

curl -s "https://stroomprijzenapi.nl/api/v1/keys/me?apiKey=sp_live_xxxx"

That answers with your tier, your limit and what you spent per day. Counts staying low is not by itself a fault: keyed URLs are cached, so what you see there is a lower bound.