Skip to main content

engie-nl

Async Python client for the private API behind the ENGIE Energie NL app (nl.engie.engieapp). It covers the whole thing: all 148 endpoints the app declares, with 199 typed models.

The reads a household actually polls are on the client itself: the customer record and EANs, daily consumption per meter, meter readings, the termijnbedrag advice, invoices, the smart-meter data mandate, outages, and day-ahead prices. The rest is grouped by area, client.tariffs, client.assets, client.enode and so on, listed below.

ENGIE does not document or support this API. It can change without notice. The map it is built from, with the receipts, lives in apk-reverse-engineering/docs/engie-nl/ENGIE_NL_API.md.

How the login works

The app logs in through Okta (login.engie.nl) and sends the Okta access token straight to the gateway as Authorization: Bearer. ENGIE's org runs Okta Identity Engine, so this package walks the interaction code flow: interact, introspect, identify, then the password.

ENGIE then emails a one-time code, and it is not optional. login() asks Okta to send the mail and raises EngieEmailCodeRequired; hand the code to submit_email_code() to finish. Only the token pair is kept, never the password. The scope includes offline_access, so this happens once: the refresh token carries every later session without touching the mailbox.

import asyncio
from engie_nl import EngieClient, EngieEmailCodeRequired, OktaAuth

async def main():
    async with OktaAuth() as auth:
        try:
            tokens = await auth.login("you@example.com", "password")
        except EngieEmailCodeRequired as err:
            code = input("code from the email: ")
            tokens = await auth.submit_email_code(err.challenge, code)

        async with EngieClient(tokens, auth=auth) as client:
            user = await client.get_user()
            for series in await client.get_consumptions(user.eans, days=7):
                for day in series.data:
                    print(series.ean, day.day, day.total, "kWh/m3")

asyncio.run(main())

Persist tokens.to_dict() and rebuild with TokenSet.from_dict(); losing the refresh token means another trip to the mailbox.

Pass on_tokens_updated= to be told when the pair is refreshed, and persist TokenSet.to_dict(). Home Assistant passes its own aiohttp session via session=.

The whole surface

Group What it covers
client.get_* user, consumptions, meterstands, estimations, transactions, documents, mandates, outages, day-ahead prices, MER periods, opening hours
client.tariffs the contract's own rates, GET /api/v1/tariffs
client.meter filing and withdrawing meter readings, the P4 feed, dongle activation
client.billing invoice payment status, documents, the MER report, the termijnbedrag, iDEAL
client.account profile, settings cards, areas of interest, passwords, payment details
client.mandates granting and withdrawing the smart-meter mandate
client.assets declared solar panels, heat pumps, batteries, cars, chargers, aircos
client.enode linked vehicles and chargers, locations, charge policies, sessions
client.smart_charging ENGIE's own smart-charging programme
client.happy_hour announced Happy Hours, subscriptions, payouts
client.solar solar potential, quotes, home and energy scans
client.address postcode lookup, iDIN verification, moving the contract
client.support opening hours, waiting time, advice articles, feedback, chat
client.ev charge card and charging station lead forms
client.legacy pre-Okta authentication and account creation
Net2GridClient the P1 dongle, on Net2Grid's own host

tools/check_coverage.py compares the package against the APK map and is run by the test suite, so "all 148" stays true rather than being a claim in a README.

Writes are off unless you ask

Every method that changes the account raises EngieWriteBlocked on a normal client. The endpoints behind them are not test fixtures: POST /api/v1/meterstands files a meter reading with the supplier who bills you, PUT /api/v1/prepayment changes a direct debit, and POST /api/v1/contract/move moves the contract to another address.

async with EngieClient(tokens, auth=auth, allow_writes=True) as client:
    await client.billing.set_prepayment(user.eans, amount=195)

Two POSTs are queries despite the verb, /api/v1/readings and /api/v1/p4-errors, and need no permission: both send a body to read P4 data back.

Scripts

  • scripts/login.py: interactive login. Prompts for the password, then for the code ENGIE emails, and stores the token pair at ~/.config/engie-nl/tokens.json (mode 0600). --check runs the whole login and saves nothing; --browser uses the browser flow instead.
  • scripts/probe.py: reads every supported endpoint once and writes the raw responses to ~/.config/engie-nl/captures/ (mode 0600, override with ENGIE_CAPTURES) for inspection. They land outside the repo because a real /user answer carries the account holder's name, address, bank account and both meter EANs.

Development

uv venv .venv && uv pip install -e '.[dev]'
.venv/bin/pytest
.venv/bin/pytest --cov          # line and branch coverage, gated at 100%
.venv/bin/pylint engie_nl
.venv/bin/mypy engie_nl

Tests run the client against a loopback aiohttp server that plays both Okta and the gateway; no network, no mocks of aiohttp internals.

tests/test_coverage.py checks the package against the APK's api-map.json, which lives in a separate repo that is not published with this one. Point ENGIE_NL_API_MAP at that file, or check the repo out beside this one, and the two endpoint tests run. Without it they skip and name the path they looked for, so a skip is visible rather than a silent pass:

export ENGIE_NL_API_MAP=/path/to/apk-reverse-engineering/docs/engie-nl/api-map.json

--cov needs no other flags: the source list, branch coverage and the 100% gate are in pyproject.toml, so the number is the same wherever it is run. Every value in the tests is invented. Nothing in tests/ came from a real account; scripts/probe.py writes real responses to ~/.config/engie-nl/captures/, outside the working tree, and no test reads them. Keeping them out of the repo means no gitignore rule stands between a real bank account number and a public push.

Metadata

Release files for engie-nl 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for engie-nl 0.2.1
File Size Uploaded
engie_nl-0.2.1.tar.gz 85.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for engie-nl 0.2.1
File Interpreter ABI Platform
engie_nl-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 153.6 kB

Release files / engie_nl-0.2.1.tar.gz

Download URL engie_nl-0.2.1.tar.gz
Size 85.3 kB
Tags Source
SHA-256 checksum
How to use checksums
32074f4df0d2362d5c6a1d3e231533d836ac1c76d5f68a05a46286cc449b734d
BLAKE2b-256 checksum
How to use checksums
afea6f005184801f38477fe978b6af05b1d3ceab64e7b6258d461255f64c5172
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / engie_nl-0.2.1-py3-none-any.whl

Download URL engie_nl-0.2.1-py3-none-any.whl
Size 68.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
60ca39678136d01c212ed25e510caad5ccd47b6b8a82fe035c4bc7b95000f629
BLAKE2b-256 checksum
How to use checksums
c5384df51300a8a45561f0b34a69be69aeb9d028854e067a7417f625e16b9fbe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page