> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Electricity Maps

> Client-mode REST API connector for the Electricity Maps API v4: grid carbon intensity, electricity mix, day-ahead prices, renewable and carbon-free share, load, net load, cross-border flows and per-source generation as latest, history, past and forecast, plus zone lookup by key or coordinates

Technical Preview

> [!WARNING]
>
> **API key and plan.** Every data endpoint needs an Electricity Maps API key in the `auth-token` header. Which signals, zones and granularities a key can reach depends on its plan (14-day trial or paid). Call `ListZones` with your key to see what each zone allows.

## Overview

[Electricity Maps](https://www.electricitymaps.com) publishes hourly and sub-hourly data about the electricity on the grid in about 350 zones worldwide: how carbon-intensive it is, which sources produce it, what it costs on the day-ahead market, and forecasts of all of these. The **API v4** (`https://api.electricitymaps.com/v4`) is documented at [app.electricitymaps.com/docs](https://app.electricitymaps.com/docs).

The **ElectricityMapsApi** connector maps 35 MQTT topics onto that API, all read-only `GET` calls. Typical uses are carbon-aware scheduling (shift flexible loads such as compressors, chillers, EV charging or batch processes into low-carbon or low-price hours), Scope 2 reporting per production line, and showing grid carbon intensity on a plant dashboard next to energy meters. Each event publishes the API reply on `<topic>/response`.

| Event | Topic | Request | Use |
| - | - | - | - |
| `ListZones` | `energy/electricitymaps/zone/list` | `GET /v4/zones` | All commercially available zones (zoneKey, zoneName, countryCode, tier); with a token also the endpoints each zone allows. Works with token "" |
| `GetZone` | `energy/electricitymaps/zone/get` | `GET /v4/zone?{query}` | Metadata for one zone, or resolve lat=\&lon= to a zone key; query zone=PT or lat=41.15\&lon=-8.61. Works with token "" |
| `GetUpdatedSince` | `energy/electricitymaps/updated-since/get` | `GET /v4/updated-since?{query}` | Data points of a zone changed since a datetime (to refresh stored history); query zone=\&since=\&start=\&end= (required) |
| `GetCarbonIntensityLatest` | `energy/electricitymaps/carbon-intensity/latest/get` | `GET /v4/carbon-intensity/latest?{query}` | Grid carbon intensity (gCO2eq/kWh), lifecycle or direct, flow-traced by default: latest value |
| `GetCarbonIntensityForecast` | `energy/electricitymaps/carbon-intensity/forecast/get` | `GET /v4/carbon-intensity/forecast?{query}` | Grid carbon intensity (gCO2eq/kWh), lifecycle or direct, flow-traced by default: forecast; query horizonHours= |
| `GetCarbonIntensityHistory` | `energy/electricitymaps/carbon-intensity/history/get` | `GET /v4/carbon-intensity/history?{query}` | Grid carbon intensity (gCO2eq/kWh), lifecycle or direct, flow-traced by default: last 24 h |
| `GetCarbonIntensityPast` | `energy/electricitymaps/carbon-intensity/past/get` | `GET /v4/carbon-intensity/past?{query}` | Grid carbon intensity (gCO2eq/kWh), lifecycle or direct, flow-traced by default: one past point; query datetime= (required) |
| `GetCarbonIntensityPastRange` | `energy/electricitymaps/carbon-intensity/past-range/get` | `GET /v4/carbon-intensity/past-range?{query}` | Grid carbon intensity (gCO2eq/kWh), lifecycle or direct, flow-traced by default: past range; query start=\&end= (required) |
| `GetElectricityMixLatest` | `energy/electricitymaps/electricity-mix/latest/get` | `GET /v4/electricity-mix/latest?{query}` | Generation / consumption mix per source in MW (v4 replacement of power-breakdown): latest value |
| `GetElectricityMixForecast` | `energy/electricitymaps/electricity-mix/forecast/get` | `GET /v4/electricity-mix/forecast?{query}` | Generation / consumption mix per source in MW (v4 replacement of power-breakdown): forecast; query horizonHours= |
| `GetElectricityMixHistory` | `energy/electricitymaps/electricity-mix/history/get` | `GET /v4/electricity-mix/history?{query}` | Generation / consumption mix per source in MW (v4 replacement of power-breakdown): last 24 h |
| `GetElectricityMixPast` | `energy/electricitymaps/electricity-mix/past/get` | `GET /v4/electricity-mix/past?{query}` | Generation / consumption mix per source in MW (v4 replacement of power-breakdown): one past point; query datetime= (required) |
| `GetElectricityMixPastRange` | `energy/electricitymaps/electricity-mix/past-range/get` | `GET /v4/electricity-mix/past-range?{query}` | Generation / consumption mix per source in MW (v4 replacement of power-breakdown): past range; query start=\&end= (required) |
| `GetDayAheadPriceLatest` | `energy/electricitymaps/price-day-ahead/latest/get` | `GET /v4/price-day-ahead/latest?{query}` | Day-ahead wholesale price (EU), EUR/MWh: latest value |
| `GetDayAheadPriceForecast` | `energy/electricitymaps/price-day-ahead/forecast/get` | `GET /v4/price-day-ahead/forecast?{query}` | Day-ahead wholesale price (EU), EUR/MWh: forecast; query horizonHours= |
| `GetDayAheadPriceActual` | `energy/electricitymaps/price-day-ahead/actual/get` | `GET /v4/price-day-ahead/actual?{query}` | Day-ahead wholesale price (EU), EUR/MWh: settled / published prices; query start=\&end= (required) |
| `GetDayAheadPriceCombined` | `energy/electricitymaps/price-day-ahead/combined/get` | `GET /v4/price-day-ahead/combined?{query}` | Day-ahead wholesale price (EU), EUR/MWh: actuals + forecast in one reply |
| `GetDayAheadPriceHistory` | `energy/electricitymaps/price-day-ahead/history/get` | `GET /v4/price-day-ahead/history?{query}` | Day-ahead wholesale price (EU), EUR/MWh: last 24 h |
| `GetRenewableEnergyLatest` | `energy/electricitymaps/renewable-energy/latest/get` | `GET /v4/renewable-energy/latest?{query}` | Renewable share of electricity (%): latest value |
| `GetRenewableEnergyForecast` | `energy/electricitymaps/renewable-energy/forecast/get` | `GET /v4/renewable-energy/forecast?{query}` | Renewable share of electricity (%): forecast; query horizonHours= |
| `GetCarbonFreeEnergyLatest` | `energy/electricitymaps/carbon-free-energy/latest/get` | `GET /v4/carbon-free-energy/latest?{query}` | Carbon-free share (renewables + nuclear, %): latest value |
| `GetCarbonFreeEnergyForecast` | `energy/electricitymaps/carbon-free-energy/forecast/get` | `GET /v4/carbon-free-energy/forecast?{query}` | Carbon-free share (renewables + nuclear, %): forecast; query horizonHours= |
| `GetTotalLoadLatest` | `energy/electricitymaps/total-load/latest/get` | `GET /v4/total-load/latest?{query}` | Total electricity load (MW): latest value |
| `GetTotalLoadForecast` | `energy/electricitymaps/total-load/forecast/get` | `GET /v4/total-load/forecast?{query}` | Total electricity load (MW): forecast; query horizonHours= |
| `GetNetLoadLatest` | `energy/electricitymaps/net-load/latest/get` | `GET /v4/net-load/latest?{query}` | Net load = load minus wind and solar (MW): latest value |
| `GetNetLoadForecast` | `energy/electricitymaps/net-load/forecast/get` | `GET /v4/net-load/forecast?{query}` | Net load = load minus wind and solar (MW): forecast; query horizonHours= |
| `GetElectricityFlowsLatest` | `energy/electricitymaps/electricity-flows/latest/get` | `GET /v4/electricity-flows/latest?{query}` | Cross-border import / export flows per neighbour zone (MW): latest value |
| `GetCarbonIntensityLevelLatest` | `energy/electricitymaps/carbon-intensity-level/latest/get` | `GET /v4/carbon-intensity-level/latest?{query}` | Traffic-light level (high / moderate / low) of carbon intensity vs the zone's recent history: latest value |
| `GetCarbonIntensityLevelForecast` | `energy/electricitymaps/carbon-intensity-level/forecast/get` | `GET /v4/carbon-intensity-level/forecast?{query}` | Traffic-light level (high / moderate / low) of carbon intensity vs the zone's recent history: forecast; query horizonHours= |
| `GetRenewablePercentageLevelLatest` | `energy/electricitymaps/renewable-percentage-level/latest/get` | `GET /v4/renewable-percentage-level/latest?{query}` | Traffic-light level of the renewable share: latest value |
| `GetCarbonIntensityFossilOnlyLatest` | `energy/electricitymaps/carbon-intensity-fossil-only/latest/get` | `GET /v4/carbon-intensity-fossil-only/latest?{query}` | Carbon intensity of the fossil part of the mix only: latest value |
| `GetElectricitySourceLatest` | `energy/electricitymaps/electricity-source/latest/get` | `GET /v4/electricity-source/{source_type}/latest?{query}` | One generation source (source\_type = solar, wind, nuclear, ...), MW: latest value |
| `GetElectricitySourceForecast` | `energy/electricitymaps/electricity-source/forecast/get` | `GET /v4/electricity-source/{source_type}/forecast?{query}` | One generation source (source\_type = solar, wind, ...), MW: forecast; query horizonHours= |
| `GetLmpDayAheadLatest` | `energy/electricitymaps/lmp-day-ahead/latest/get` | `GET /v4/locational-marginal-price-day-ahead/latest?{query}` | US day-ahead locational marginal price for a pricing node; query node= (required) |
| `GetSignal` | `energy/electricitymaps/signal/get` | `GET /v4/{signal}/{variant}?{query}` | Any other documented v4 data path: signal = total-reported-load, variant = latest (or past, past-range, ...) |

## Authentication

Every request sends `auth-token: {value.json.token}`. Keep the API key in a broker secret, for example `ELECTRICITY_MAPS_TOKEN`, and map it into the payload with `GET SECRET`. `ListZones` and `GetZone` answer without a key, so send `token = ""` for them. A wrong key on `ListZones` returns `401 Token is invalid`; the data endpoints return `401 Invalid auth-token` without a valid key.

## Choosing a zone

Pass `zone=PT` (zone keys come from `ListZones`) or `lat=` and `lon=` in `query`. `GetZone` turns coordinates into a zone key once, for example `lat=41.15&lon=-8.61` returns `PT` for Porto. Without a zone or coordinates the API falls back to the location of the caller's IP address, which for a broker in a data center is usually the wrong grid. Add `disableCallerLookup=true` to turn that fallback off.

## Request payload

Every event reads its values from the JSON payload published on the request topic:

| Field | Used by | Example |
| - | - | - |
| `token` | all events | `(GET SECRET "ELECTRICITY_MAPS_TOKEN")`, or `""` for `ListZones` / `GetZone` |
| `query` | all events except `ListZones` | `zone=PT&temporalGranularity=15_minutes` |
| `source_type` | `GetElectricitySourceLatest`, `GetElectricitySourceForecast` | `solar`, `wind`, `nuclear`, `hydro`, `gas`, `coal`, `biomass`, `geothermal`, `oil`, `unknown`, `hydro-discharge`, `battery-discharge` |
| `signal`, `variant` | `GetSignal` | `total-reported-load` and `latest` |

Common query parameters are `temporalGranularity` (`5_minutes`, `15_minutes` or `hourly`), `emissionFactorType` (`lifecycle` or `direct`), `flowTraced` (`true` for consumption-based values, `false` for the zone's own production), `disableEstimations`, `horizonHours` for forecasts, `datetime` for a past point and `start` plus `end` for a past range (ISO 8601, UTC). Leave `query` as `""` when an event needs no parameters, which leaves a harmless trailing `?` on the URL.

## Connector template

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE ElectricityMapsApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://api.electricitymaps.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '30'
        WITH DEFAULT_CONTENT_TYPE "application/json"
        # Smoke: GetZone (lat/lon Porto, token "") -> ListZones (with token) -> GetCarbonIntensityLatest (zone=PT) -> GetElectricityMixLatest -> GetCarbonIntensityForecast.
    ADD EVENT ListZones
        WITH SOURCE_TOPIC "energy/electricitymaps/zone/list"
        WITH DESTINATION_TOPIC "energy/electricitymaps/zone/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v4/zones"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "auth-token" "{value.json.token}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT GetZone
        WITH SOURCE_TOPIC "energy/electricitymaps/zone/get"
        WITH DESTINATION_TOPIC "energy/electricitymaps/zone/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v4/zone?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "auth-token" "{value.json.token}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    # ... 33 more events in the package file (ElectricityMapsRestApi.lot)
```

The complete connector with all 35 events is in the package file `ElectricityMapsRestApi.lot`.

## Example actions

These actions poll Portugal (`PT`) at about 6 calls per hour. Replace the zone with your own.

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Example actions for the ElectricityMapsApi route (docs page). Not part of the route template.
# Secret: ELECTRICITY_MAPS_TOKEN = the API key from the Electricity Maps portal (sent as the auth-token header).
# Zone PT = Portugal (mainland grid, Tier A). Replace it with your own zone key (ListZones / GetZone).
# Inline JSON object literals are not supported when publishing to a topic, so the request payload is a COLLAPSED
#   model sent with PUBLISH MODEL.
# Budget: about 4 + 1 + 1 + 0.04 = 6 calls per hour. Hourly data changes once an hour; 15-minute polling only
#   helps with temporalGranularity=15_minutes or 5_minutes on plans that include them.

DEFINE MODEL ElectricityMapsRequest COLLAPSED
    ADD STRING "token"
    ADD STRING "query"

DEFINE ACTION GridCarbonIntensityEvery15Minutes
ON EVERY 15 MINUTES DO
    PUBLISH MODEL ElectricityMapsRequest TO "energy/electricitymaps/carbon-intensity/latest/get" WITH
        token = (GET SECRET "ELECTRICITY_MAPS_TOKEN")
        query = "zone=PT&disableCallerLookup=true"

DEFINE ACTION GridMixHourly
ON EVERY 1 HOUR DO
    PUBLISH MODEL ElectricityMapsRequest TO "energy/electricitymaps/electricity-mix/latest/get" WITH
        token = (GET SECRET "ELECTRICITY_MAPS_TOKEN")
        query = "zone=PT&disableCallerLookup=true"

DEFINE ACTION GridCarbonForecastHourly
ON EVERY 1 HOUR DO
    PUBLISH MODEL ElectricityMapsRequest TO "energy/electricitymaps/carbon-intensity/forecast/get" WITH
        token = (GET SECRET "ELECTRICITY_MAPS_TOKEN")
        query = "zone=PT&horizonHours=24&disableCallerLookup=true"

DEFINE ACTION DayAheadPriceForecastDaily
ON EVERY 1 DAY DO
    PUBLISH MODEL ElectricityMapsRequest TO "energy/electricitymaps/price-day-ahead/forecast/get" WITH
        token = (GET SECRET "ELECTRICITY_MAPS_TOKEN")
        query = "zone=PT&horizonHours=48&disableCallerLookup=true"
```

## Testing

1. Store your key: add the secret `ELECTRICITY_MAPS_TOKEN` on the broker.
2. Deploy `ElectricityMapsRestApi.lot`.
3. Publish `{"token": "", "query": "lat=41.15&lon=-8.61"}` to `energy/electricitymaps/zone/get` and check that `energy/electricitymaps/zone/get/response` returns `"zoneKey":"PT"`.
4. Publish `{"token": "<your key>", "query": "zone=PT"}` to `energy/electricitymaps/carbon-intensity/latest/get` and check that the response carries `carbonIntensity` and `datetime`.

## Notes and limits

* Version: this connector uses v4. The v3 paths still answer, but in v4 the old `power-breakdown` endpoints are called `electricity-mix`.
* Hourly data changes once an hour. Polling faster only helps with `5_minutes` or `15_minutes` granularity on plans that include it.
* `GetLmpDayAheadLatest` is for US pricing nodes (`node=` is required); `price-day-ahead` covers European zones.
* Marginal emission signals were discontinued by Electricity Maps and are not part of this connector.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.