Skip to main content

bayernwerk-client

Release PyPI Test Report License: MIT Renovate uv Ruff

Inoffizielle Python-Clients für Bayernwerk-Onlinedienste - reverse-engineered, nicht von Bayernwerk/E.ON unterstützt oder autorisiert. Nutzung auf eigene Verantwortung und nur mit einem eigenen, berechtigten Account.

Das Package ist nach Service aufgeteilt:

  • bayernwerk_client.map - Mein.Auftragsportal (meinauftragsportal.html / REST-API icon-api.eon.com)
  • bayernwerk_client.efix - e-fix Installateur-Portal (bayernwerk.e-fix.info / GraphQL-API backend.e-fix.info), gleiche Zugangsdaten wie MAP

Beide sitzen hinter demselben Salesforce-Aura-Login (nur unterschiedliche Communities: account.bayernwerk-netz.de bzw. login.e-fix.info), daher gilt der folgende Abschnitt für beide.

Warum ein Browser fürs Login?

bayernwerk-netz.de sitzt hinter einer Cloudflare-JS-Challenge, und die Login-Seite selbst ist keine einfache HTML-Form, sondern eine Salesforce Experience-Cloud-App (Aura-Framework mit signierten, sessionbehafteten Requests). Beides lässt sich nicht robust mit reinem requests/httpx nachbauen. Deshalb übernimmt Playwright einmalig den Login (Cloudflare-Challenge lösen + Formular ausfüllen) und liefert die Tokens. Alle eigentlichen API-Calls danach laufen über reines httpx - kein Browser nötig, solange der Token gültig ist.

Playwright ist deshalb eine normale (Kern-)Abhängigkeit, keine optionale Extra - ohne funktionierenden Login ist der Client kaum zu gebrauchen, also lohnt sich der Split nicht. Nötig ist trotzdem ein zusätzlicher Schritt, um die Browser-Binary selbst zu installieren (siehe unten).

Token-Lebensdauer: Access-Tokens gelten ca. 1 Stunde. Bei MAP wird zwar ein refresh_token mitgeliefert, das ist aber ein Salesforce-Community-Refresh-Token - er erneuert zwar erfolgreich gegen .../services/oauth2/token, liefert dabei aber ein natives Salesforce-Session-Token statt des JWTs, das icon-api.eon.com erwartet (das JWT entsteht in einem separaten, nicht identifizierten "Funke IAM"-Austauschschritt, den nur das SPA-JS selbst ausführt). e-fix liefert gar keinen Refresh-Token. Praktisch bedeutet das für beide: bei Ablauf einfach erneut login_interactive aufrufen (dauert nur wenige Sekunden) statt zu versuchen, den Token still zu erneuern - siehe on_token_expired im Beispiel unten.

Installation

Als CLI-Tool (empfohlen):

uv tool install bayernwerk-client
playwright install chromium

Als Abhängigkeit in einem eigenen Python-Projekt (Library-Nutzung, siehe unten):

uv add bayernwerk-client

Zugangsdaten

map und efix nutzen denselben Bayernwerk-Netz-Account (dieselbe E-Mail/Passwort-Kombination aus dem Mein.Auftragsportal). Der login-Befehl jedes Services (und der automatische Re-Login bei abgelaufenem Token) liest die Zugangsdaten aus zwei Umgebungsvariablen:

export MAP_EMAIL="deine@email.de"
export MAP_PASSWORD="dein-passwort"

Sind sie nicht gesetzt, fragt bayernwerk map login / bayernwerk efix login interaktiv danach (E-Mail per input(), Passwort per getpass - landet nicht in der Shell-History). Der automatische Re-Login bei abgelaufenem Token funktioniert dagegen nur, wenn die Variablen gesetzt sind - sonst bricht der jeweilige Befehl mit einer Fehlermeldung ab, die zum erneuten manuellen login auffordert (siehe on_token_expired in den cli.py-Modulen).

bayernwerk sucht die Zugangsdaten in dieser Reihenfolge (die erste gefundene gewinnt, nichts wird überschrieben, was schon gesetzt ist):

  1. Bereits exportierte Umgebungsvariablen.
  2. Eine .env-Datei (siehe unten).
  3. ~/.config/bayernwerk-client/credentials.toml (siehe unten) - als dauerhafter, verzeichnisunabhängiger Default fürs eigene System.

Aus einer .env-Datei laden, statt die Zugangsdaten in jeder Shell neu zu exportieren: bayernwerk lädt beim Start automatisch eine .env aus dem aktuellen (oder einem übergeordneten) Verzeichnis, via python-dotenv. Bereits gesetzte Umgebungsvariablen haben immer Vorrang vor der .env-Datei.

# .env (liegt bereits in .gitignore - nie committen!)
MAP_EMAIL=deine@email.de
MAP_PASSWORD=dein-passwort
bayernwerk map login   # liest MAP_EMAIL/MAP_PASSWORD automatisch aus .env

Kein manuelles source nötig. Wer lieber ein allgemeineres Tool dafür nutzt: direnv lädt eine .envrc automatisch beim Betreten des Verzeichnisses (dann auch für andere Tools verfügbar, nicht nur bayernwerk).

Dauerhaft in ~/.config/bayernwerk-client/credentials.toml hinterlegen, falls du nicht in jedem Arbeitsverzeichnis eine eigene .env pflegen willst - praktisch z.B. nach uv tool install, wenn du bayernwerk von überall aus aufrufst. Für einen einzelnen Account reichen flache KEY = "value"-Paare:

# ~/.config/bayernwerk-client/credentials.toml
MAP_EMAIL = "deine@email.de"
MAP_PASSWORD = "dein-passwort"

Für mehrere Accounts (z.B. mehrere Installateur-Logins) benannte Profile als eigene Sektionen, ausgewählt über den Login-Namen:

[general]
DEFAULT = "dglaser"          # optional - welches Profil ohne weitere Angabe gilt

[dglaser]
MAP_EMAIL = "dglaser@example.com"
MAP_PASSWORD = "passwort-1"

[azubi]
MAP_EMAIL = "azubi@example.com"
MAP_PASSWORD = "passwort-2"

Welches Profil verwendet wird (erste zutreffende Regel gewinnt):

  1. bayernwerk --profile azubi ... (globales Flag, wie -j).
  2. BAYERNWERK_PROFILE=azubi (echte Umgebungsvariable oder .env).
  3. [general].DEFAULT in der Datei, falls gesetzt.
  4. Die einzige vorhandene Profil-Sektion, falls nur eine existiert - dann muss kein DEFAULT angegeben werden.
  5. Sonst keins - flache Top-Level-Werte (falls vorhanden) gelten trotzdem weiterhin, nur die Profil-Sektionen selbst werden dann nicht herangezogen. Das ist kein Fehler. Ein explizit angefordertes Profil (Punkt 1-3), das es nicht gibt, ist dagegen ein Fehler - [general] selbst zählt dabei nie als Profil.

Wie bei .env: Datei enthält ein Klartext-Passwort, also chmod 600 ~/.config/bayernwerk-client/credentials.toml. Alle Werte müssen wie in TOML üblich in Anführungszeichen stehen (DEFAULT = "...", nicht DEFAULT = ...).

Die gecachten Tokens (nicht die Zugangsdaten selbst) landen danach lokal unter ~/.cache/bayernwerk-client/map-tokens.json bzw. .../efix-tokens.json, mit restriktiven Dateirechten (chmod 600).

CLI

bayernwerk ist der Einstiegspunkt, map/efix sind die Subcommands je Service - jeder cacht seinen Token getrennt (map-tokens.json / efix-tokens.json), ein Login gilt nicht für den jeweils anderen Service. -j/--json ist ein globales Flag vor dem Service (bayernwerk -j map orders, nicht bayernwerk map orders -j) und schaltet JSON- statt menschenlesbare Ausgabe für jeden beliebigen Befehl ein. bayernwerk --help, bayernwerk map --help bzw. bayernwerk efix --help zeigen die Befehle direkt im Terminal inkl. aller Argumente.

bayernwerk map - Mein.Auftragsportal

Befehl Beschreibung
login [--headless] Einmalig einloggen (sichtbarer Browser, außer mit --headless) und Token cachen. Nötig bevor irgendein anderer map-Befehl funktioniert.
orders [-s] [-f|-o] Alle Aufträge auflisten. Menschenlesbar als ein Absatz pro Auftrag mit Kopfzeile (Status, Datum, Auftragsnummer, Kunde, Produkt, Adresse - wie im Portal), Rest eingerückt darunter. -s/--short gibt nur die Kopfzeile aus, eine pro Zeile (wirkt nicht auf -j). -f/--finished bzw. -o/--open filtern auf abgeschlossene bzw. offene Aufträge (schließen sich gegenseitig aus, auch kombinierbar mit -s, z.B. -fs).
order ORDER_ID Einen einzelnen Auftrag anzeigen (Rohdaten der orders-Liste, gefiltert auf eine Auftragsnummer).
order-details ORDER_ID Zusätzliche Detaildaten zu einem Auftrag (separater Endpunkt als order).
order-documents ORDER_ID Dokumente eines Auftrags auflisten (Dateiname, Typ, Scan-Status, ...) - zum Download siehe sync-documents.
order-notes ORDER_ID Notizen zu einem Auftrag auflisten.
order-instances ORDER_ID Der "Anschluss"-Baum eines Auftrags: Zähler → Verbraucher/Erzeuger → Wechselrichter → Speicher/PV, als eingerückter Baum (menschenlesbar) bzw. flache Liste (-j).
sync-documents ORDER_ID FOLDER Dokumente eines Auftrags mit einem lokalen Ordner abgleichen: lädt nur die Dokumente herunter, die dort (per Dateiname) noch fehlen; vorhandene Dateien bleiben unangetastet. FOLDER wird bei Bedarf angelegt.
installer Eigene Installateur-Stammdaten (Firma, Adresse, Kontakt) laut MAP-Backend.
inverters --primary-energy-form FORM Wechselrichter-Stammdaten. FORM ist Pflicht (z.B. PV oder AC_STORAGE) und mehrfach angebbar.
storages Speicher-Stammdaten (verfügbare Modelle/Kapazitäten).
product-orders Produktaufträge auflisten (separat von orders).

bayernwerk efix - e-fix Installateur-Portal

Befehl Beschreibung
login [--headless] Einmalig einloggen (eigener Token-Cache, unabhängig von map) und Token cachen.
installer Eigene Installateur-Stammdaten laut e-fix-Backend (Firma, Adresse, Netzbetreiber-Zuordnung, Ausweis-Status, ...) - inhaltlich umfangreicher als map installer, da e-fix eigene Stammdaten pflegt.
antraege Eigene Anträge ("Antraege") mit Status, Typ/Subtyp und Eingangsdatum.
status Account-Status: Rolle, Benachrichtigungszähler, Abo-Status, Name/E-Mail.
events Registrierte Veranstaltungen/Schulungen mit Terminen.

Shell-Completion

bayernwerk completion install

Erkennt die Shell automatisch ($SHELL, bash/zsh) und trägt eine eval-Zeile in ~/.bashrc bzw. ~/.zshrc ein (idempotent - beim zweiten Aufruf passiert nichts). Neue Shell starten oder die rc-Datei neu sourcen, danach vervollständigt Tab sowohl Services (map/efix) als auch alle Subcommands auf jeder Ebene, inklusive --primary-energy-form & Co. Läuft über argcomplete und wird direkt aus der tatsächlichen argparse-Struktur generiert - kein händisch gepflegtes Completion-Skript, das veralten könnte.

Andere Shell oder manuelle Einrichtung: bayernwerk completion bash (bzw. zsh/fish) gibt nur das Skript aus, zum selbst Einbinden. Für Fish z.B. bayernwerk completion fish > ~/.config/fish/completions/bayernwerk.fish.

Verwendung als Library

from bayernwerk_client.map import MAP_TOKEN_PATH, MapClient, TokenStore
from bayernwerk_client.map.auth import login_interactive

store = TokenStore(MAP_TOKEN_PATH)
tokens = store.load()
if tokens is None or tokens.is_expired:
    tokens = login_interactive("email@example.com", "passwort", headless=False)
    store.save(tokens)

with MapClient(tokens, token_store=store) as client:
    for order in client.list_orders():
        print(order)

Siehe examples/list_orders.py für ein vollständiges Beispiel inklusive automatischem Re-Login bei abgelaufenem Token. bayernwerk_client.efix funktioniert analog (EfixClient, EFIX_TOKEN_PATH, bayernwerk_client.efix.auth.login_interactive).

API-Oberfläche (bayernwerk_client.map)

MapClient deckt die bekannten icon-api.eon.com-Endpunkte mit Convenience-Methoden ab (list_orders, get_order, get_order_details, get_order_instances, get_order_documents, get_order_notes, download_order_document, sync_order_documents, list_installers, list_inverters, list_storages, list_product_orders). Für alles andere steht die generische client.request(method, path, **httpx_kwargs) zur Verfügung.

Anschluss-Baum (Zähler/Wallbox/Wärmepumpe/Wechselrichter/...)

get_order_instances liefert die Baumstruktur hinter dem "Anschluss"-Tab im Portal (Zähler → Verbraucher/Erzeuger → Wechselrichter → Speicher/PV, ...). Die Verschachtelung variiert je nach Ausstattung (itemType/schemaType) und Bayernwerk kann jederzeit neue Gerätetypen ergänzen - ein starres Datenmodell dafür würde ständig hinterherlaufen. iter_instance_items läuft den Baum deshalb schemalos ab: alles mit einem itemType-Feld wird als Knoten erkannt, egal unter welchem Schlüssel (meters, actors, inverterGroups, inverters, ...) es hängt, und Duplikate (manche Akteure tauchen im Rohformat zweimal auf - einmal direkt, einmal nochmal verschachtelt unterm zugehörigen Wechselrichter) werden über ivyId herausgefiltert.

for item in client.iter_order_instance_items("2577481104"):
    print(item["itemType"], "/", item.get("schemaType"), "-", item.get("formData"))

bayernwerk_client.map.iter_instance_items(tree) ist die reine Funktion dahinter, falls du bereits ein get_order_instances-Ergebnis vorliegen hast.

Dokumente eines Auftrags mit einem lokalen Ordner abgleichen

downloaded = client.sync_order_documents(
    "2577481104", "/pfad/zum/kundenordner"
)
print(f"{len(downloaded)} neue Dokument(e) heruntergeladen:", downloaded)

Vergleicht die Dokumentenliste des Auftrags mit den vorhandenen Dateien im Ordner (per Dateiname) und lädt nur fehlende Dokumente herunter. Bestehende Dateien werden nicht angerührt.

tenant (Default BAG) und lang (Default de) werden automatisch an jeden Request angehängt - das Backend antwortet ohne diese beiden Query-Parameter mit einem 500er. list_inverters braucht zusätzlich primary_energy_forms (z.B. ["PV"]).

API-Oberfläche (bayernwerk_client.efix)

EfixClient ist ein GraphQL-Client (nicht REST wie MAP) für backend.e-fix.info: client.query(query, operation_name=..., variables=...) für beliebige Queries/Mutations, plus Convenience-Methoden für die bekannten Queries: get_installer(), list_installer_antraege(), get_user_status(), list_my_registered_events().

Package-Struktur

bayernwerk_client/
├── exceptions.py     # BayernwerkClientError, AuthenticationError, ApiError - service-uebergreifend
├── formatting.py      # generische Absatz-/Dict-Formatierung fuers CLI - service-uebergreifend
├── _jwt.py            # generische JWT-Payload-Dekodierung - service-uebergreifend
├── tokens.py           # generischer JWT-Token-Store (TokenSet/TokenStore) - service-uebergreifend
├── completion.py       # `bayernwerk completion` - argcomplete-Setup, kein eigener Service
├── cli.py             # `bayernwerk`-Einstiegspunkt, registriert Service-Subcommands
├── map/                # Mein.Auftragsportal (REST)
│   ├── client.py, auth.py, instances.py
│   ├── formatting.py   # `render_instance_tree`, `render_orders` (map-spezifisch)
│   └── cli.py           # `map`-Subcommand
└── efix/                # e-fix Installateur-Portal (GraphQL)
    ├── client.py, auth.py
    └── cli.py           # `efix`-Subcommand

Entwicklung

uv sync
uv run pytest --cov=bayernwerk_client --cov-report=term-missing
pre-commit run --all-files

Test-Report & Coverage: the78mole.github.io/bayernwerk-client

  • bei jedem Push nach main neu erzeugt (.github/workflows/pages.yml, via GitHub Pages/Actions-Deploy) und zeigt Testergebnisse sowie den interaktiven Coverage-Report.

Metadata

Release files for bayernwerk-client 0.0.8

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

Source distribution (sdist)

Source distribution for bayernwerk-client 0.0.8
File Size Uploaded
bayernwerk_client-0.0.8.tar.gz 75.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bayernwerk-client 0.0.8
File Interpreter ABI Platform
bayernwerk_client-0.0.8-py3-none-any.whl Python 3 none any Details

Total release size: 110.4 kB

Release files / bayernwerk_client-0.0.8.tar.gz

Download URL bayernwerk_client-0.0.8.tar.gz
Size 75.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e698956bf4779777308dba8f4bab8b7aaeed0ea8ed4a381a169309f35a4b260d
BLAKE2b-256 checksum
How to use checksums
238964da9e009862931a83b6a43bf1c4172b17989ef3d8e11f5692dd86f28f1a
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 Aug 17, 2026.

Transparency log

Release files / bayernwerk_client-0.0.8-py3-none-any.whl

Download URL bayernwerk_client-0.0.8-py3-none-any.whl
Size 34.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
90e90f99d3d6f29d2e18cef488e1844e7236c0ddce0abf469a76064a18b04cb7
BLAKE2b-256 checksum
How to use checksums
852e123d12b765319e4ec763b6e7100ca521331a605c46b6c1c9fc9c7cbbe156
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 Aug 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.8 This release

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

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