pyzonneplan
Asynchronous Python client for the Zonneplan API.
About
pyzonneplan is an async client for the Zonneplan API, focused on:
- Dynamic electricity and gas prices
- Electricity and gas consumption, also without a P1 meter
- Solar (PV) installation data
- Home battery monitoring and control modes
- EV charge point sessions and schedules
The library is under active development and endpoint coverage will keep expanding.
Installation
pip install pyzonneplan
Usage
Zonneplan authenticates with a one-time password (OTP) mailed to the account address instead of a password. Log in once, then store the token and pass it back in later:
import asyncio
import json
from datetime import date
from pathlib import Path
from pyzonneplan import Token, Zonneplan
TOKEN_FILE = Path("token.json")
async def main() -> None:
"""Log in (or restore the saved token) and read some data."""
token = Token.from_dict(json.loads(TOKEN_FILE.read_text())) if TOKEN_FILE.exists() else None
async with Zonneplan(email="you@example.com", token=token) as client:
if token is None:
challenge = await client.async_request_otp(source_name="pyzonneplan")
await client.async_submit_otp(challenge, input("One-time password: "))
account = await client.async_get_account()
for connection in account.connections:
print(connection.market_segment, connection.uuid)
electricity = next(c for c in account.connections if c.market_segment == "electricity")
summary = await client.async_get_summary(electricity.uuid)
print("Tariff group now:", summary.usage.type)
chart = await client.async_get_electricity_chart(electricity.uuid, date.today())
if chart.group is not None and chart.group.has_data:
print("Used today:", chart.group.delivered_kwh, "kWh")
# The client refreshes the token when it nears expiry, which rotates it: save the latest one.
if client.token is not None:
TOKEN_FILE.write_text(json.dumps(client.token.as_dict()))
if __name__ == "__main__":
asyncio.run(main())
Available data
| Method | Returns |
|---|---|
async_get_account() |
The account, its addresses, connections and contracts |
async_get_consumer_prices(chart) |
Electricity (hourly or quarter-hourly) or daily gas prices, with price_at() and cheapest/most expensive helpers |
async_get_summary(connection_uuid) |
Live usage (P1 only), the current tariff group and a price forecast of about two days |
async_get_electricity_chart(connection_uuid, day, interval) |
Electricity used and returned per hour, day or month |
async_get_gas_chart(connection_uuid, day, interval) |
Gas used per hour, day or month |
async_get_electricity_delivered(connection_uuid) / async_get_gas(connection_uuid) |
P1 totals and live readings (meters), or None without a P1 meter |
async_get_pv_installation(connection_uuid) |
Every solar inverter on the connection and today's yield, or None without solar panels |
async_get_battery(connection_uuid, contract_uuid) |
Home battery state, results and modes |
async_get_battery_chart(contract_uuid, day, interval) |
Home battery results per day or month |
async_get_battery_control_mode(contract_uuid) / async_get_battery_home_optimization(contract_uuid) |
The battery's control mode, and the charge and discharge power for home optimization with the range each accepts |
async_get_charge_point(connection_uuid, contract_uuid) |
Charge point state, schedules and vehicles |
async_set_locale(locale) |
Sets the language of the API's texts, e.g. nl-NL |
The contract UUIDs come from the account, e.g. connection.contracts_of_type(ContractType.HOME_BATTERY)
(constants such as ContractType, ChartInterval and BatteryMode live in pyzonneplan.const).
For a contract the account doesn't have, the battery and charge point methods raise ZonneplanNotFoundError.
Fields keep the API's raw units (1e-7 EUR, Wh, dm³, permille); properties such as delivered_kwh,
electricity_price_euro and state_of_charge_percent convert them.
Usage without a P1 meter arrives a day or more late from the grid operator: check has_data before trusting zeros,
and expect the last hours of a day to be revised when the next day arrives.
The PV, battery and charge point models follow the responses other projects have seen, but haven't been tested against live hardware yet. Reports and captured (anonymised) responses are welcome.
Actions
| Method | Does |
|---|---|
async_enable_battery_self_consumption(connection_uuid, contract_uuid) / async_disable_... |
Turns self consumption mode on or off |
async_enable_battery_home_optimization(connection_uuid, contract_uuid, *, max_charge_power_w, max_discharge_power_w) / async_disable_... |
Turns home optimization mode on (with new power limits, or the current ones) or off |
async_set_battery_backup_reserve(connection_uuid, contract_uuid, reserved_wh) |
Reserves energy for backup power |
async_set_battery_control_mode(connection_uuid, contract_uuid, mode) |
Switches to a BatteryMode, turning the other modes off the way the app does |
async_start_charge_point_boost(connection_uuid, contract_uuid) / async_stop_charge_point(...) |
Starts charging now at full power, or stops charging |
async_resume_charge_point_auto_charging(connection_uuid, contract_uuid) |
Lets the charge point charge on cheap prices again after a manual stop |
async_start_charge_point_dynamic_session(connection_uuid, contract_uuid, end, *, kilometers or percentage, vehicle_uuid) |
Charges an amount by end at the cheapest prices |
async_reset_charge_point_schedule(connection_uuid, contract_uuid) |
Clears the planned charging |
A battery with both modes off trades on the dynamic prices (BatteryMode.DYNAMIC_CHARGING). The API doesn't
switch the other mode off for you; async_set_battery_control_mode() does, like the Zonneplan app. The battery
and charge point confirm a change asynchronously; until then their state reports processing.
Actions are sent once and never retried.
Errors
Every error derives from ZonneplanError:
ZonneplanAuthenticationError: the token is invalid or expired (log in again);ZonneplanInvalidOtpErrorwhen the OTP is rejected.ZonneplanRateLimitError: HTTP 429;retry_afterholds the seconds to wait. It isn't retried.ZonneplanRequestError: the API rejected the request (HTTP 4xx), e.g. an unknown chart interval;ZonneplanNotFoundErrorfor a 404, e.g. a device the account doesn't have. Not retried.ZonneplanResponseError: the response isn't JSON or doesn't have the expected shape.ZonneplanConnectionError/ZonneplanTimeoutError: a network error, a timeout or an HTTP 5xx. GET requests are retried with backoff first (max_retries, default 3); actions are never retried.
More examples can be found in the examples directory.
Documentation
Project documentation and API reference: https://erwindouna.github.io/pyzonneplan/
Contributing
Contributions are welcome. Please open an issue or pull request.
For local development:
uv sync --all-groups && uv run pre-commit install
Run checks:
uv run pre-commit run --all-files
Run tests:
uv run pytest
License
MIT License
Copyright (c) 2026 Erwin Douna
Metadata
Release files for pyzonneplan 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyzonneplan-0.2.0.tar.gz | 27.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyzonneplan-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.3 kB
Release files / pyzonneplan-0.2.0.tar.gz
| Download URL | pyzonneplan-0.2.0.tar.gz |
|---|---|
| Size | 27.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fe3960724798d8470fa2827d98dcc57b0cf4d5cddb2c055506b867ebce070b9f
|
|
BLAKE2b-256 checksum How to use checksums |
c623737bc3bd06c590ba4eef9858bd43187ec18eba99d9ad121bd50cfaa66185
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / pyzonneplan-0.2.0-py3-none-any.whl
| Download URL | pyzonneplan-0.2.0-py3-none-any.whl |
|---|---|
| Size | 30.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
197971314bedd8cbad1e27ef7d012a3e953728480142b16f6deef0dd7a8b7d6d
|
|
BLAKE2b-256 checksum How to use checksums |
1f1ab6c4e99b4265a0e9e833e9508bee159ca2abf9fd04c1760a81009dc34a96
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency log