stroomprijzenapi Home Assistant

Home Assistant

Everything you need to read Dutch electricity prices into Home Assistant, including the energy tax and VAT — the price you actually pay, not the bare market price.

No account, no API key, no rate limit. Copy a block below into your configuration.yaml, restart, and you have a sensor. The full endpoint reference lives at /docs.

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 exactly why people ship it by accident.

1. The price right now

The smallest useful thing. One sensor, updated every five minutes. It is the quickest way to see something working — but it asks us the same question 288 times a day, and the answer only changes on the hour. Section 6 builds the same sensor out of today's prices with no repeated requests at all; once this one works, go and read that.

rest:
  - resource: https://api.stroomwekker.nl/api/v1/prices/now
    scan_interval: 300
    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

The attributes give you the full breakdown, so a card can show what the price is made of rather than only the total.

Add ?resolution=quarterhour to the URL for quarter-hourly prices. The Dutch day-ahead market has traded on quarter-hours since 1 October 2025; an hourly price is simply the mean of its four quarters.

2. All of today, and tomorrow

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

rest:
  - resource_template: >-
      https://api.stroomwekker.nl/api/v1/prices?date={{ now().strftime('%Y-%m-%d') }}
    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_template: >-
      https://api.stroomwekker.nl/api/v1/prices?date={{ (now() + timedelta(days=1)).strftime('%Y-%m-%d') }}
    scan_interval: 3600
    sensor:
      - name: "Electricity prices tomorrow"
        unique_id: stroomprijzen_tomorrow
        value_template: "{{ value_json.count }}"
        json_attributes:
          - date
          - count
          - summary
          - points

Those intervals are chosen for the data, not for the clock: today's prices cannot change any more, and tomorrow's arrive once, in the afternoon. Section 6 replaces both timers with two automations and gets it down to two or three requests a day.

Each entry in points looks like this, so a template can reach any part of it:

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

Set Home Assistant's time zone to Europe/Amsterdam. now() returns your instance's local time, and a day here is a Dutch calendar day. If Home Assistant is on UTC, now() rolls over to the next date two hours late in summer and you fetch the wrong day for the first two hours after midnight.

Tomorrow is empty until the afternoon

Tomorrow's prices appear around 14:00 CE(S)T. Before then the endpoint returns count: 0 and an empty points array — a normal 200, not an error. Guard your templates for it rather than treating it as a failure.

3. Useful derived sensors

These are starting points; adjust the thresholds to your own habits. They assume the sensors from the previous sections.

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

      - name: "Cheapest price today"
        unique_id: stroomprijs_cheapest_price
        unit_of_measurement: "EUR/kWh"
        state_class: measurement
        state: >-
          {% set s = state_attr('sensor.electricity_prices_today', 'summary') %}
          {{ s.minEurKwh if s else none }}

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

4. Charting the day

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.

5. The Energy dashboard

Home Assistant's Energy dashboard can take a price entity for grid consumption: 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 actually pay per kWh — excluding the fixed parts of your bill, which no per-kWh price can express. Standing charges, the annual tax rebate and your supplier's markup all sit outside this API; see the reference for exactly what is and is not included.

6. How often to fetch: two or three times a day

Day-ahead prices do not trickle in. They change twice in twenty-four hours and are otherwise completely still:

A published day never changes again. So a well-written client needs two or three requests a day: today's prices once, tomorrow's once it exists, and a retry or two in the afternoon while waiting for it. Not one every five minutes. Everything else — the price this hour, the cheapest window, whether now is a good moment — can be worked out from those numbers locally, because Home Assistant already has them.

With a key, every call reaches us. A request carrying Authorization: Bearer bypasses the CDN cache entirely — that is what makes the usage counts exact, and it is the deal you accept when you use a key. Without one you mostly hit the cache. Either way the polite number is the same; with a key it is also the honest one.

Fetch on a schedule, not on a timer

Set the REST sensors to a long scan_interval and drive them from automations instead. That turns "every 15 minutes, forever" into "when there is something new".

rest:
  - resource_template: >-
      https://api.stroomwekker.nl/api/v1/prices?date={{ now().strftime('%Y-%m-%d') }}
    scan_interval: 86400
    sensor:
      - name: "Electricity prices today"
        # ... as in section 2

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

That is two calls on a normal day and four on a day 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, which is the one way a handful of polite clients can still behave like a spike.

The current price, without asking

Today's prices are already in Home Assistant, so the price this hour is a lookup, not a request. This replaces the REST sensor from section 1 — same entity name, same value, zero traffic.

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 it needs Home Assistant on Europe/Amsterdam — as does everything else here. On quarter-hourly data, match '%Y-%m-%dT%H:%M' against a quarter-hour boundary instead, or keep the hourly resolution for this sensor and the finer one for charts.

If you would rather just poll

Nothing above is enforced, and a plain scan_interval is a perfectly reasonable place to start. These numbers are polite:

What you are readingReasonable scan_interval
Current price900 (15 minutes) — or 0 requests, see above
Today's prices21600 (6 hours); they cannot change
Tomorrow's prices3600, or 900 between 13:00 and 16:00

Responses are served from a CDN with cache headers matched to how stable they are, so polling faster mostly returns you the same cached bytes. There is no rate limit worth noticing today; clients that fetch on the data's rhythm rather than on a stopwatch are what keeps it that way.

7. Checking the 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 the API rather than agree with it by construction.

8. When something looks wrong

The price is far lower than my energy bill

You are almost certainly 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 — pass ?date= and let the API work it out.

Today has 23 or 25 entries

That is correct on the clock-change days in March and October. Never assume 24, and never line two series up by array position — match on momentUtc.

Tomorrow is empty

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