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), sobald das Package auf PyPI liegt:

uv tool install bayernwerk-client
playwright install chromium

Aktuell noch nicht auf PyPI - bis dahin direkt aus dem Repo installieren:

uv tool install git+https://github.com/the78mole/bayernwerk-client
playwright install chromium

Als Abhängigkeit in einem eigenen Python-Projekt (Library-Nutzung, siehe unten): uv add bayernwerk-client bzw. uv add git+https://github.com/the78mole/bayernwerk-client, solange noch nicht auf PyPI.

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.3.tar.gz (63.3 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.3-py3-none-any.whl (32.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: bayernwerk_client-0.0.3.tar.gz
  • Upload date:
  • Size: 63.3 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.3.tar.gz
Algorithm Hash digest
SHA256 14cdbe28aff59b41d52a7828749f473f76555b88031f5a441122fcd04baf02e6
MD5 6a116dd43164ad51066a056e234c35b8
BLAKE2b-256 c56148a707f0aa6f68a5be41fe80694554acefaa2dcd191b3f8932ede95c1e82

See more details on using hashes here.

Provenance

The following attestation bundles were made for bayernwerk_client-0.0.3.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.3-py3-none-any.whl.

File metadata

File hashes

Hashes for bayernwerk_client-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 3e42be6da8a84b8591584200cf2870d5187fa56a1794ce089f36eebc7779a0af
MD5 6bcd5145ee21729a5e476071125bc5b5
BLAKE2b-256 d50f919eedbbdb733d76c3a7a40957c12125cd80d666353d8c28adf85edb5577

See more details on using hashes here.

Provenance

The following attestation bundles were made for bayernwerk_client-0.0.3-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