Danish Electricity Prices: Spot + Full Consumer Price per kWh avatar

Danish Electricity Prices: Spot + Full Consumer Price per kWh

Pricing

from $20.00 / 1,000 full consumer-price days

Go to Apify Store
Danish Electricity Prices: Spot + Full Consumer Price per kWh

Danish Electricity Prices: Spot + Full Consumer Price per kWh

DK1/DK2 spot prices plus the real price per kWh for your grid company (tariffs, elafgift, VAT), cheapest hours and the best charging window.

Pricing

from $20.00 / 1,000 full consumer-price days

Rating

0.0

(0)

Developer

Nordic Open Data

Nordic Open Data

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

9 days ago

Last modified

Share

Danish Electricity Prices: Spot + Full Consumer Price per kWh (DK1/DK2)

Get today's and tomorrow's Danish electricity prices as you actually pay them: the day-ahead spot price for DK1 or DK2 plus your grid company's tariff, Energinet's tariffs, elafgift (electricity tax), 25% VAT and, if you want, your supplier's markup and subscriptions. Give your grid company (name or GLN, both on your electricity bill) and the Actor finds the right tariff and price area for you. A postcode works as a best-effort alternative for about half of Danish postcodes.

It also tells you when to use power: the cheapest N hours of each day and the cheapest contiguous block (for example the best 3 hours to charge an EV, or the best 90 minutes for a dishwasher).

What it does

  • 15-minute day-ahead spot prices for DK1 (West Denmark) and DK2 (East Denmark), in DKK/kWh and EUR/MWh

  • Full consumer price per kWh by grid company: spot + grid tariff (Nettarif C) + Energinet transmission and system tariffs + elafgift + VAT

  • Grid company by name or GLN (fuzzy: "radius", "cerius", "N1 A/S - 131"; an exact full official name such as "TREFOR El-net A/S" always wins over fuzzy matches), price area derived automatically; postcode lookup as a best-effort fallback (about 50% coverage from open data, weaker in Jutland)

  • Optional supplier markup and monthly subscriptions, shown separately and optionally spread per kWh from your annual consumption

  • Cheapest hours and cheapest contiguous window (in hours or quarter-hours) per day and across the whole range

  • Tomorrow mode: returns tomorrow's prices once they're published (normally just after 13:00 CET), and otherwise a clear NOT_YET_PUBLISHED status

  • Date ranges up to 31 days (from 2025-10-01, when Denmark moved to 15-minute prices)

  • Clean, stable, documented output with ISO 8601 timestamps that carry the UTC offset

  • Home Assistant summary: one small record with the current price, next 24 h, cheapest block from now and min/max/average (mode: "summary")

  • EV charging plan: the cheapest time to charge before your deadline, with expected cost in DKK (mode: "evCharging")

Use cases

Smart-home and EV charging schedules, heat-pump control, Home Assistant and Node-RED automations, energy dashboards, price alerts, comparison sites and AI agents that need a reliable Danish price feed.

Home Assistant and automations (mode: "summary")

One small JSON record (about 2 KB) instead of 96–192 rows: the current 15-minute price, the next 24 hours as hourly prices, the cheapest block of whole hours starting after now, min/max/average and how the current price compares with the average. Prices are the full variable price per kWh incl. VAT (spot only if you leave out the grid company).

{ "mode": "summary", "gridCompany": "Radius Elnet A/S", "windowHours": 3 }

Result (shortened):

{
"mode": "summary", "status": "OK", "priceArea": "DK2", "unit": "DKK/kWh",
"current": { "start": "2026-10-15T21:30:00+02:00", "end": "2026-10-15T21:45:00+02:00", "price": 2.2215 },
"min": { "start": "2026-10-16T13:00:00+02:00", "price": 1.9802 },
"max": { "start": "2026-10-16T19:00:00+02:00", "price": 3.8004 },
"average": 2.5418, "currentVsAverage": -0.126,
"cheapestWindow": { "start": "2026-10-16T00:00:00+02:00", "end": "2026-10-16T03:00:00+02:00", "hours": 3, "averagePrice": 2.0455 },
"hoursAvailable": 24, "tomorrowPublished": true,
"next24h": [{ "start": "2026-10-15T21:00:00+02:00", "price": 2.2486 }, "…"]
}

The record is both the single dataset item and the OUTPUT record, so either API call works:

  • POST https://api.apify.com/v2/acts/nordic_open_data~dk-electricity-consumer-prices/run-sync returns the record itself (the OUTPUT record).
  • POST …/run-sync-get-dataset-items returns [record].

Home Assistant REST sensor (configuration.yaml; put Bearer <your Apify API token> in secrets.yaml as apify_bearer):

rest:
- resource: https://api.apify.com/v2/acts/nordic_open_data~dk-electricity-consumer-prices/run-sync
method: POST
headers:
Authorization: !secret apify_bearer
Content-Type: application/json
payload: '{"mode": "summary", "gridCompany": "Radius Elnet A/S", "windowHours": 3}'
timeout: 60
scan_interval: 3600 # each poll is one paid run, see Pricing
sensor:
- name: "Electricity price now"
unique_id: dk_elpris_now
value_template: "{{ value_json.current.price }}"
unit_of_measurement: "DKK/kWh"
json_attributes: [next24h, cheapestWindow, min, max, average, currentVsAverage, tomorrowPublished]
- name: "Cheapest 3h block start"
unique_id: dk_elpris_cheapest_start
value_template: "{{ value_json.cheapestWindow.start if value_json.cheapestWindow else 'unknown' }}"
device_class: timestamp

Automation: start the dishwasher when the cheapest block begins.

automation:
- alias: "Dishwasher in the cheapest block"
trigger:
- platform: time
at: sensor.cheapest_3h_block_start
action:
- service: switch.turn_on
target: { entity_id: switch.dishwasher }

Generic automation (Node-RED, n8n, Make, cron, scripts): call run-sync with the same JSON and read current.price, cheapestWindow.start or next24h. Example with curl:

curl -s -X POST -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
-d '{"mode":"summary","gridCompany":"Radius Elnet A/S"}' \
https://api.apify.com/v2/acts/nordic_open_data~dk-electricity-consumer-prices/run-sync | jq .cheapestWindow

Tip: prices are fixed a day ahead, so you don't need a poll every 15 minutes. Polling once in the afternoon (after 13:30) plus a few times a day is enough: the next24h attribute lets Home Assistant templates pick the current hour's price between polls.

EV charging plan (mode: "evCharging")

Estimate, not a quote. The expected cost uses published spot prices, your grid company's household tariff (C), Energinet tariffs, elafgift, VAT and the markup you enter. Reduced elafgift, supplier-specific or hourly fees, other tariff types and subscriptions are not included, so your bill can differ. Check your supplier's terms. Every plan carries this note in its disclaimer field.

Tell it how much energy you need, your charger power and when the car must be ready. It finds the cheapest continuous charging period using full consumer prices (spot + tariffs + elafgift + your markup + VAT) at 15-minute resolution, and returns the start, end and expected cost.

{ "mode": "evCharging", "gridCompany": "Radius Elnet A/S",
"evEnergyKwh": 40, "chargerKw": 11, "chargingEfficiency": 0.9, "readyBy": "07:00" }

Result (shortened):

{
"mode": "evCharging", "status": "OK",
"start": "2026-10-16T00:30:00+02:00", "end": "2026-10-16T04:33:00+02:00", "durationMinutes": 242,
"energyToBatteryKwh": 40, "energyFromGridKwh": 44.444, "chargerKw": 11, "efficiency": 0.9,
"expectedCostDkk": 90.91, "averagePriceDkkPerKwh": 2.0455,
"chargeImmediately": { "start": "2026-10-15T21:45:00+02:00", "costDkk": 94.59 }, "savingVsImmediateDkk": 3.68,
"window": { "earliestStart": "2026-10-15T21:37:00+02:00", "readyBy": "2026-10-16T07:00:00+02:00" },
"schedule": [{ "start": "2026-10-16T00:30:00+02:00", "kwh": 2.75, "price": 2.0431 }, "…"]
}
  • readyBy: "07:00" means the next 07:00 after the start, or give a date-time like "2026-10-16T07:00" (Danish local time). The window can be up to 48 hours.
  • earliestStart: empty means now; charging starts at the next quarter-hour.
  • chargingEfficiency (optional, default 1): the battery gets kWh, the grid delivers kWh ÷ efficiency, and you pay for the grid energy. The last quarter-hour is charged only partly when the energy doesn't fill it.
  • Cost covers energy only (variable price incl. VAT); fixed subscriptions don't depend on when you charge.
  • Infeasible requests fail clearly and cost nothing, for example: Not feasible: charging 100 kWh at 2 kW takes 50 h 0 min, but only 9 h 15 min with published prices is available …. If readyBy is later than the last published price (before about 13:00 for tomorrow), the plan uses the published hours and says so in warnings.
  • Needs the full consumer price, so a grid company (or postcode) is required.
  • Daylight-saving days are handled: on 25 October 2026 the 02:00–03:00 hour occurs twice (100 quarter-hours), and plans and windows run on real UTC time, so durations and costs stay correct; times are shown with their +02:00 / +01:00 offset.

Input

FieldTypeDefaultDescription
modeprices | summary | evChargingpricesprices: one row per 15 minutes. summary: one compact record for automations. evCharging: one charging plan. In compact modes period/startDate are ignored
periodtoday | tomorrow | todayAndTomorrow | last7Days | last30Days | customtodayAndTomorrowWhich days to fetch (Danish local dates). last7Days/last30Days are complete past days ending yesterday
startDate, endDateYYYY-MM-DD–Only with custom. Both dates are included; maximum 31 days
gridCompanySelectdropdown–Easiest in the Console. Pick your grid company from the list of all 32 with a household tariff (labelled with area/towns). Overrides gridCompany. In API/JSON input this is a bare GLN, so prefer gridCompany there
gridCompanystring– (no prefill)Recommended for the API. Full name ("Radius Elnet A/S"), short name (radius), a former name ("Ørsted Net", "SEAS-NVE Net") or the 13-digit GLN
postcode4 digits–Best-effort alternative (about half of postcodes covered). If it can't be resolved, the run asks for gridCompany
priceAreaDK1 | DK2derivedOnly needed for spot-only runs with no location
includeTariffsbooleantrue (on by default)Return the full consumer price. Needs gridCompany or postcode; turn off for spot prices only
tariffCodestringautoAdvanced: force a specific DatahubPricelist tariff code
supplierMarkupDkkPerKwhnumber0Supplier markup (tillæg), DKK/kWh, entered incl. VAT (shown excl. VAT in the output, see FAQ)
gridSubscriptionDkkPerMonthnumber0Grid subscription, DKK/month incl. VAT
supplierSubscriptionDkkPerMonthnumber0Supplier subscription, DKK/month incl. VAT
annualConsumptionKwhinteger–Required with a subscription: fixed costs are spread per kWh into fixedCostsDkkPerKwh and totalPriceDkkPerKwh. Without subscriptions fixedCostsDkkPerKwh is 0
cheapestHours0–243Number of cheapest individual hours per day to flag
windowHours0–243Length of the cheapest contiguous block, in hours
windowQuarterHours0–960Length of the cheapest contiguous block, in 15-minute steps
evEnergyKwh, chargerKw, readyBynumber, number, string–Required for evCharging (kWh to add, charger kW, "07:00" or "YYYY-MM-DDTHH:MM")
earliestStart, chargingEfficiencystring, numbernow, 1Optional for evCharging
onlyFuturebooleanon (Console checkbox shows ON)Rank cheapest hours/windows only among intervals that start after the run time, so the advice is never in the past. Applied only when the period contains today; fully past or future ranges are ranked in full. Past rows are still returned with prices; they just get no rank. OUTPUT.onlyFuture shows whether it was applied
failOnEmptybooleanfalseFail the run when there's no data or the sanity checks fail (for monitoring)

Invalid input stops the run before any data is fetched or charged. All problems are reported together in one message (and as a list in OUTPUT.errors), for example Invalid input (2 problems): (1) startDate must be 2025-10-01 or later … (2) No grid company matches …. Other examples: postcode must be a 4-digit Danish postcode, Postcode 9999 does not exist in Denmark (vs. Postcode 8000 (Aarhus C) exists but isn't covered by the open-data postcode table…), Date range is 45 days; maximum is 31, or Ambiguous grid company for name "N1": N1 A/S - 016 (grid area 016, DK1) = GLN 5790002502699; N1 A/S - 131 (…) = GLN 5790001089030; N1 A/S - 344 (…) = GLN 5790000611003. Pick one in the grid company list, or set gridCompany to its full name or GLN. Failed runs also write an OUTPUT record with status: "ERROR", the message, charged: false and any candidates, and are never charged.

Example inputs

Tomorrow's full price for a Radius customer, with supplier costs:

{ "period": "tomorrow", "gridCompany": "Radius", "supplierMarkupDkkPerKwh": 0.10,
"gridSubscriptionDkkPerMonth": 45, "supplierSubscriptionDkkPerMonth": 29, "annualConsumptionKwh": 4000,
"cheapestHours": 3, "windowHours": 3 }

Spot only, DK2, one week:

{ "period": "custom", "startDate": "2026-10-05", "endDate": "2026-10-11", "priceArea": "DK2", "includeTariffs": false }

By grid company name, best 90-minute window:

{ "gridCompany": "radius", "windowQuarterHours": 6 }

Output

  • Dataset: one row per 15-minute interval with the spot price, every price component, variablePriceDkkPerKwh (what you pay per kWh incl. VAT, excl. subscriptions), totalPriceDkkPerKwh (incl. subscriptions spread per kWh) and cheapest-hour flags. Views: Prices, Full price breakdown, Spot prices only and Cheapest hours & windows. Cheapest-hour fields (cheapestHourRank, isCheapestHour, inCheapestHourWindow, inCheapestQuarterWindow) appear only when you use cheapestHours, windowHours or windowQuarterHours.
  • Spot-only runs (includeTariffs: false) return only spot fields; tariff, VAT, total and grid-company fields are left out rather than empty, and the status message says Spot only: add gridCompany for the full price.
  • OUTPUT record: a summary with status, averages, cheapest hours and windows, billing, and on failure status: "ERROR", the message and candidates.

Full field list and examples: see Technical below.

FAQ

When are tomorrow's prices available? The day-ahead auction closes at 12:00 CET and prices normally show up on Energi Data Service shortly after 13:00 CET. Before that, tomorrow returns status: NOT_YET_PUBLISHED and an empty dataset. The run still succeeds unless failOnEmpty is set.

Why 15-minute prices? Since 1 October 2025 the Nordic day-ahead market settles in 15-minute periods. Grid tariffs are still hourly. For hourly values, use the cheapestHours summary or average the four quarter-hours.

Does it match my bill exactly? It comes close for typical households on time-of-use tariffs, but it's an estimate. Suppliers can add fees, use different rounding, or bill on hourly averages.

Name or GLN in the API? Use the name ("gridCompany": "Radius Elnet A/S"). It's readable in your code and logs, survives copy-paste, gives helpful suggestions on typos and works with former names. A GLN is exact but opaque: a wrong digit silently means another company (testers picked Cerius when they meant Radius). Use the GLN only if you already store it, or for N1/Konstant, which have several areas (or use the full name with suffix, e.g. "N1 A/S - 131").

My grid company has a different name on old bills. Former names are recognised: DONG Energy Eldistribution / Ørsted Net → Radius, SEAS-NVE Net → Cerius, Energi Fyn Net → Vores Elnet, NRGi Net / Energi Horsens Net → Konstant (pick area 151 or 245), Eniig / Evonet / SE Net / HEF Net / EnergiMidt Net → N1 (pick area 016, 131 or 344). Sources are listed under Technical.

Why does my 0.40 kr markup show as 0.32? You enter markup and subscriptions incl. VAT, as suppliers advertise them. In the output, all component fields (supplierMarkupDkkPerKwh, the tariffs, elafgift) are excl. VAT, so 0.40 shows as 0.32. The VAT on all parts is in vatDkkPerKwh, and variablePriceDkkPerKwh, fixedCostsDkkPerKwh and totalPriceDkkPerKwh are incl. VAT, so the 0.40 is fully in the price you see.

Grid company vs postcode – which wins? The grid company list wins over the free-text grid company, which wins over the postcode. If you set both a list choice and free text that point to different companies, the list is used and a warning appears in the status message and OUTPUT.warnings. If you set a grid company and a postcode, they must agree: a postcode served by another company stops the run with an error (nothing is charged); a postcode the open-data table can't check (e.g. 8000 Aarhus C) gives a warning and the grid company is used.

Which dataset view should I use? Prices (full consumer price) for normal runs; Prices (spot only) for spot-only runs, so you don't get empty tariff/total columns. Every price column says whether it is incl. or excl. VAT.

Which Konstant / N1 area am I? The list shows towns where known: Konstant 151 = Aarhus area, Konstant 245 = Favrskov, Horsens, Odder, Skanderborg, Syddjurs (per public tariff sites), TREFOR El-net Øst = Bornholm, TREFOR El-net = Triangle Region, Elektrus = Helsingør town (not Bornholm). N1's three areas aren't published per town; the grid area number is on your bill. If unsure, use the postcode.

Where's the list of just the cheapest intervals? Dataset views can't filter rows, so the cheapest view shows every interval with its rank/flags. OUTPUT.selectedIntervals holds only the selected intervals (ranked hours and window intervals), sorted by time.

Why doesn't the cheapest window show 12:00 when I run at 21:00? By default (onlyFuture) a run for today ranks only intervals that haven't started yet, so the advice is something you can still act on. Set onlyFuture: false to rank the whole day (e.g. for analysis), and OUTPUT.rankedFrom shows the cut-off.

Which grid company am I with? It's on your electricity bill (name and GLN). Pick it in the grid company list, or type it in gridCompany. A postcode works only where the open-data table can resolve it (about half of postcodes, weaker in Jutland).

Can I schedule it? Yes. A daily schedule after 13:30 CET with period: "tomorrow" and failOnEmpty: true gives you a reliable feed and an alert if something breaks.

What if tariffs are missing for my grid company? Consumer-price runs are all-or-nothing. Before saving or charging anything, the Actor checks that every row of every day has a grid tariff, Energinet tariffs, elafgift and a total price. If anything is missing, the run fails with a clear error naming the day and fields, returns no data and charges nothing. Missing tariffs are never silently counted as 0. Retry later, set tariffCode, or use includeTariffs: false for spot prices only.

Is "Include full consumer price" on by default? Yes. Since build 0.2.13, includeTariffs defaults to on, so you need gridCompany (or a postcode) unless you switch it off. If you switch it off but still give a grid company, the output warns that only spot prices were returned.

How is my grid company matched? A GLN always matches exactly. An exact full official name (case-insensitive, e.g. "Elnet Midt A/S", "L-Net A/S") wins over similar names. Short or fuzzy names ("trefor", "N1") that fit several companies return an error listing the candidates with GLNs.

Is it affiliated with Energinet? No. See the attribution below.

Output schema version 3 (build 0.2.23, 15 Oct 2026). The output field supplierMarkupDkkPerKwh is renamed supplierMarkupExVatDkkPerKwh (the value was always excl. VAT; the input supplierMarkupDkkPerKwh is unchanged and still entered incl. VAT). Rows that aren't among the cheapest hours now omit cheapestHourRank (blank in tables) instead of null. Check schemaVersion if you parse the output.

Pricing

Pay per event. You pay only for results; there are no platform usage charges on top.

EventPriceWhen
Actor start (actor-start)$0.005Once per run that returns prices
Spot-price day (spot-price-day)$0.01Per day returned, spot only (includeTariffs: false)
Consumer-price day (consumer-price-day)$0.02Per day returned with the full consumer price
Summary record (summary-record)$0.004Per summary record (mode: summary), instead of a day event
EV charging plan (ev-charging-plan)$0.02Per EV plan (mode: evCharging), instead of a day event

Examples: today + tomorrow with the full price = $0.005 + 2 × $0.02 = $0.045; one day spot only = $0.015. Summary and EV modes charge one actor-start plus one record event, whatever the number of days the record covers: $0.009 per summary ($0.005 + $0.004), $0.025 per EV plan ($0.005 + $0.02). If your maximum cost per run is below that, nothing is charged and no record is returned. Polling a summary every hour costs about $0.22 a day (≈ $6.5/month); 4 polls a day ≈ $1.1/month (see the Home Assistant tip).

Not charged: failed runs (including consumer-price runs stopped because a tariff or total was missing), invalid input, and runs where prices aren't published yet (NOT_YET_PUBLISHED) or no data exists. If your maximum cost per run is lower than the full request, the Actor returns only the days it can charge for and says so in OUTPUT.warnings.

Limitations

  • Denmark only (DK1/DK2). Only household (C) tariffs.
  • The postcode table is an open-data snapshot that covers about half of all postcodes. Elsewhere, and for postcodes that cross grid boundaries, set gridCompany.
  • Data before 2025-10-01 (hourly Elspotprices) isn't supported.
  • Reduced elafgift for electric heating, grid subscriptions (unless entered) and supplier-specific fees aren't modelled.
  • Results depend on the availability of Energi Data Service.

Data: Energi Data Service (Energinet), CC BY 4.0. Postcode lookup: derived from © OpenStreetMap contributors (ODbL 1.0) and GeoNames (CC BY 4.0). Not affiliated with Energinet. Details under Technical.

Technical

Background for developers and the curious: exact output format, price formula, how the postcode table is built and data licences.

Output in detail

Dataset: one row per 15-minute interval. All prices are DKK/kWh unless the field name says otherwise.

{
"intervalStart": "2026-10-15T13:00:00+02:00",
"intervalEnd": "2026-10-15T13:15:00+02:00",
"intervalStartUtc": "2026-10-15T11:00:00Z",
"date": "2026-10-15", "hour": 13, "resolutionMinutes": 15,
"priceArea": "DK2", "currency": "DKK",
"spotPriceDkkPerKwh": 0.76952, "spotPriceEurPerMwh": 102.94,
"gridTariffDkkPerKwh": 0.31852, "transmissionTariffDkkPerKwh": 0.043, "systemTariffDkkPerKwh": 0.072,
"electricityTaxDkkPerKwh": 0.008, "supplierMarkupExVatDkkPerKwh": 0.08,
"variableExVatDkkPerKwh": 1.29104, "vatDkkPerKwh": 0.32276, "variablePriceDkkPerKwh": 1.6138,
"fixedCostsDkkPerKwh": 0.222, "totalPriceDkkPerKwh": 1.8358,
"cheapestHourRank": 1, "isCheapestHour": true, "inCheapestHourWindow": true, "inCheapestQuarterWindow": true,
"gridCompany": "Radius Elnet A/S", "gridCompanyGln": "5790000705689", "gridTariffCode": "DT_C_01",
"schemaVersion": 3,
"attribution": "Source: Energi Data Service (Energinet), https://www.energidataservice.dk/, CC BY 4.0. Processed and derived by this Actor; not endorsed by Energinet."
}

Price fields:

  • variablePriceDkkPerKwh: everything you pay per kWh, incl. VAT, excluding subscriptions
  • totalPriceDkkPerKwh: the variable price plus fixed costs spread per kWh (equal to the variable price when annualConsumptionKwh isn't set)
  • Component fields (*TariffDkkPerKwh, electricityTaxDkkPerKwh, supplierMarkupExVatDkkPerKwh) exclude VAT. VAT is in vatDkkPerKwh.
  • For spot-only runs these fields are omitted.

Key-value store record OUTPUT (summary):

{
"status": "OK", "message": "192 intervals for DK2, 2 day(s).", "warnings": [],
"priceArea": "DK2", "startDate": "2026-10-15", "endDate": "2026-10-16",
"gridCompany": { "name": "Radius Elnet A/S", "gln": "5790000705689", "resolvedVia": "name \"radius\"", "tariffCode": "DT_C_01", "tariffName": "Nettarif C" },
"fixedCosts": { "totalDkkPerMonth": 74, "totalDkkPerYear": 888, "annualConsumptionKwh": 4000, "amortizedDkkPerKwh": 0.222 },
"averages": { "spotPriceDkkPerKwh": 1.45853, "variablePriceDkkPerKwh": 2.54142, "totalPriceDkkPerKwh": 2.76342 },
"cheapestHours": [{ "date": "2026-10-15", "hour": 13, "start": "2026-10-15T13:00:00+02:00", "rank": 1, "averagePriceDkkPerKwh": 1.47 }],
"cheapestHourWindow": { "perDay": [{ "date": "2026-10-15", "start": "2026-10-15T12:00:00+02:00", "end": "2026-10-15T15:00:00+02:00", "length": 3, "unit": "hour", "averagePriceDkkPerKwh": 1.64345 }],
"wholeRange": { "start": "2026-10-15T12:00:00+02:00", "end": "2026-10-15T15:00:00+02:00", "length": 3, "unit": "hour", "averagePriceDkkPerKwh": 1.64345 } },
"cheapestQuarterWindow": { "perDay": [], "wholeRange": null },
"attribution": "…"
}

status is one of OK, PARTIAL (some days aren't published yet), NOT_YET_PUBLISHED, NO_DATA, CHARGE_LIMIT_REACHED, HEALTH_CHECK_FAILED or ERROR. mode is consumer-price or spot-only. On failure:

{ "status": "ERROR", "message": "Ambiguous grid company for name \"N1\": …", "charged": false, "candidates": [{ "name": "N1 A/S - 131", "gln": "5790001089030", "label": "N1 A/S - 131 (grid area 131, DK1)" }] }

The dataset has four views: Prices (overview), Full price breakdown, Spot prices only and Cheapest hours & windows. Each cheapest-hour field is omitted when its option is 0.

How the price is calculated (assumptions)

  • Spot: DayAheadPrices from Energi Data Service, DKK/MWh ÷ 1000.
  • Grid tariff: the grid company's household time-of-use tariff, Nettarif C / Nettarif C time (customers on the 0.4 kV grid), from DatahubPricelist. It uses the price for each Danish local hour (Price1–Price24) and handles validity periods, including mid-range tariff changes. Rebate, production, "lokal kollektiv", B and A tariffs are ignored. Where a company publishes several C codes with identical prices (flex/time variants) the choice makes no difference. Where their prices differ, the time-of-use variant is used and a warning is added. tariffCode overrides the choice.
  • Energinet: transmission net tariff (40000) and system tariff (41000).
  • Elafgift: the EA-001 rate in DatahubPricelist (0.008 DKK/kWh in 2026). Reduced rates for electric heating aren't modelled.
  • VAT: 25% on all per-kWh parts. Your markup and subscriptions are entered incl. VAT, the way Danish suppliers show them.
  • Not included: grid and Energinet subscriptions unless you enter them, nettabstarif for TSO-connected customers, B/A customer tariffs and any supplier-specific fees you don't enter.
  • Cheapest hours/windows are ranked on variablePriceDkkPerKwh (or spot if tariffs are off). Fixed costs are the same every hour, so they don't change the ranking. Hours are clock hours averaged from the four quarter-hours. The whole-range window may span midnight; per-day windows don't.

Former grid company names (aliases)

data/grid-aliases.json maps historic and group names to today's company. Only verified mappings are included:

  • DONG Energy Eldistribution → Radius (Ørsted press release, Dec 2015; renamed April 2016). DONG Energy became Ørsted in 2017; Radius was sold to SEAS-NVE/Andel in 2020 and kept its name (andel.dk).
  • SEAS-NVE Net → Cerius (EnergiWatch, turn of the year 2017/18).
  • Energi Fyn Net → Vores Elnet (CVR 25587987 name history, 29 May 2018).
  • NRGi Net → Konstant Net (Konkurrence- og Forbrugerstyrelsen decision 16 Jan 2018; CVR 21262498, which also lists Energi Horsens Net).
  • Eniig's grid → N1 (1 July 2018); Evonet (SE) merged into N1 after the SE/Eniig (Norlys) merger; the earlier N1 came from EnergiMidt and HEF (Green Power Denmark). Group names that own several grid companies (e.g. Andel, Norlys, EWII) are deliberately not aliased.

Postcode validity

Postcodes are first checked against the full GeoNames list of Danish postcodes (data/postcodes-dk.json, 1,159 codes, CC BY 4.0). Unknown numbers get "does not exist"; real postcodes the open-data grid table can't resolve get "not covered".

Postcode to grid company

The Actor ships a postcode table (data/postcode-grid.json) built by tools/build_postcode_map.mjs from openly licensed sources only:

SourceUsed forLicence
GeoNames postal codes for Denmark (DK.zip)postcodes, place names, centroidsCC BY 4.0
OpenStreetMap power=substation/transformer objects with an operator tag (via Overpass)which grid company operates near the postcodeODbL 1.0, © OpenStreetMap contributors
Energi Data Service DatahubPricelist, ConsumptionGridAreaHourlist of grid companies, GLNs, tariffs, price area per companyCC BY 4.0

How a postcode is resolved: the build script takes OSM power objects within 3 km of the postcode's GeoNames centroid whose operator matches a current grid company. Operators that are transmission companies, producers, or brands that can't be matched unambiguously (e.g. "Nexel", "EnergiMidt") are ignored.

  • Resolved: at least 3 matching points, all naming the same company.
  • Several candidates: the points name more than one company. The run stops and lists the candidates so you can set gridCompany.
  • Not in the table: fewer than 3 matching points. The run stops with a clear error asking for gridCompany (name or GLN).
  • Inferred entries: none. Values are never copied from neighbouring postcodes.

Coverage of the 2026-10-15 snapshot (1,159 GeoNames postcodes): 586 resolved, 9 with several candidates, 564 not in the table, 0 inferred. Coverage is best on Zealand, Funen and in the larger cities and weakest in parts of Jutland, where OSM has few operator tags. A company with several grid areas (N1, Konstant) also needs the GLN or the "- NNN" suffix, which the error message lists. OSM data can be wrong or outdated, so if the result looks off, set gridCompany explicitly; it always takes priority over the table. Because the table is derived from OSM, it is a derivative database under ODbL; tools/ contains the script and the OSM snapshot used.

Data source and licence

Prices and tariffs: Energi Data Service (Energinet), https://www.energidataservice.dk/, licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). This Actor transforms the data: it converts units and combines spot, tariffs and taxes. It is not affiliated with or endorsed by Energinet. Every output row carries an attribution field. Postcode table sources: GeoNames (CC BY 4.0) and OpenStreetMap (ODbL 1.0, © OpenStreetMap contributors).

  • Energi Data Service terms: https://energidataservice.dk/terms-and-conditions (CC BY 4.0; copy, change and distribute, including commercially, with credit and without implying endorsement). A snapshot of the terms is kept in the source under licences/.
  • OpenStreetMap: © OpenStreetMap contributors, https://www.openstreetmap.org/copyright. data/postcode-grid.json is a derivative database of OSM data and is available under the Open Database License (ODbL) 1.0 (share-alike); the OSM snapshot and build script are included so it can be rebuilt.
  • GeoNames: postal codes from https://www.geonames.org/ under CC BY 4.0.
  • When a run resolves the grid company by postcode, each row's attribution and the OUTPUT.dataSources list also credit OpenStreetMap and GeoNames.