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.

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

Uploaded Python 3

File details

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

File metadata

  • Download URL: bayernwerk_client-0.0.7.tar.gz
  • Upload date:
  • Size: 75.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.7.tar.gz
Algorithm Hash digest
SHA256 794ddbf5defb9ad2145ec5e4fcfbf46f88dea793d913898367c27fd12e2f2bf3
MD5 c780622d53013ea006a11f2b016f5d71
BLAKE2b-256 10b573aa4ede8891ca3bb0b8fc0abd13018d97d85135add4d8c72b5f37697652

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for bayernwerk_client-0.0.7-py3-none-any.whl
Algorithm Hash digest
SHA256 868acc5d828756a1960f37d9037650889b34bbb955ae68867c75c251ba8efbab
MD5 cffae14519ca5ad3b432c278a1cb2dc8
BLAKE2b-256 9a7d8e6ecb2c1a24503cb98a0633723dbd795f79d4689b1a67df8c2b3bfcc450

See more details on using hashes here.

Provenance

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