Skip to main content

bayernwerk-client

Release PyPI 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).

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).

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bayernwerk_client-0.0.4.tar.gz (63.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bayernwerk_client-0.0.4-py3-none-any.whl (32.2 kB view details)

Uploaded Python 3

File details

Details for the file bayernwerk_client-0.0.4.tar.gz.

File metadata

  • Download URL: bayernwerk_client-0.0.4.tar.gz
  • Upload date:
  • Size: 63.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for bayernwerk_client-0.0.4.tar.gz
Algorithm Hash digest
SHA256 fd23cbc9fa75cbf40861a42f2be3ca751f7ac68f114ddd88765cca859c77c63c
MD5 b944237536ae969292a2300463bfb73e
BLAKE2b-256 eafe15a5649c32f4e92ac68174ef09261c2b793c4e3e51311d353f3b13544505

See more details on using hashes here.

Provenance

The following attestation bundles were made for bayernwerk_client-0.0.4.tar.gz:

Publisher: release.yml on the78mole/bayernwerk-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file bayernwerk_client-0.0.4-py3-none-any.whl.

File metadata

File hashes

Hashes for bayernwerk_client-0.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 d46b260eccdadbde3b76a1c6d30c9683b9f95d61522394d1aebe781d3fba5e8e
MD5 eeee93d57dbb9c8193806d4294a994b3
BLAKE2b-256 4d3264aca14f3d0224982326477ecfe430bcfd2ac430fe98aa3caca0697d1d19

See more details on using hashes here.

Provenance

The following attestation bundles were made for bayernwerk_client-0.0.4-py3-none-any.whl:

Publisher: release.yml on the78mole/bayernwerk-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page