Die Gridbert-Schnittstelle
Diese Seite dokumentiert dieselbe Schnittstelle, mit der auch app.gridbert.at rechnet. Keine zweite, abgespeckte Variante. Sie ist erzeugt, nicht getippt: Werkzeuge, Endpunkte und Grenzen kommen direkt aus dem Gateway-Code.
Was die Schnittstelle ist
Gridbert bietet zwei Wege zu denselben Werkzeugen: einen MCP-Server (unten) für Chatbots und eigene Agenten, und HTTP-Endpunkte, über die app.gridbert.at und die Kontoseiten selbst laufen. Für wen das relevant ist: alle, die einen eigenen Client, ein Skript oder einen Agenten gegen Gridbert bauen wollen. Für den fertigen Chat-Einstieg gibt es die Anleitungen je Agent.
Verbinden über MCP
Die Adresse für jeden MCP-fähigen Client:
https://mcp.gridbert.at/mcp
Verbindungsaufbau läuft über OAuth 2.1 mit Dynamic Client Registration, die Einrichtung je Agent beschreibt den Klickweg, hier die technischen Eckdaten:
| AS-Discovery | https://mcp.gridbert.at/.well-known/oauth-authorization-server |
|---|---|
| Resource-Discovery | https://mcp.gridbert.at/.well-known/oauth-protected-resource |
| JWKS | https://mcp.gridbert.at/jwks.json |
| Client-Registrierung (DCR) | https://mcp.gridbert.at/register |
| Scopes | mcp-tools offline_access |
| Grant-Typen | authorization_code, refresh_token, client_credentials |
| PKCE | S256, Pflicht |
| Token-Endpunkt-Auth | none, client_secret_post (öffentlicher Client) |
| Zugangs-Token gültig | 30 Minuten |
| Refresh-Token gültig | 30 Tage |
| Autorisierungs-Code gültig | 1 Minute |
| Protokollversion | 2025-06-18 |
Unter POST /mcp stehen vier JSON-RPC-Methoden bereit:
initialize, notifications/initialized, tools/list, tools/call. Jede Werkzeug-Antwort trägt ihr Ergebnis doppelt: als Text-Block
(content) und, wo ein Werkzeug ein Ausgabe-Schema deklariert, zusätzlich
strukturiert als structuredContent. Beide sind byte-gleich derselbe
Rechenweg, nur einmal als Text und einmal als Objekt.
Werkzeuge
27 Werkzeuge stehen jedem verbundenen Haushaltskonto zur Verfügung. Die Familie zeigt, woher ein Ergebnis kommt:
Gedächtnis liest oder schreibt dein persönliches Profil · Rechenwerk rechnet deterministisch mit energietools · Gridbert-Domäne ist Gateway-eigene Logik (Fakten, Bereitschaft, Rückfragen).
Die Beschreibung je Werkzeug ist wörtlich zitiert, so bekommt sie der Agent.
-
analyze_load_profileRechenwerk (energietools) nur lesendLastprofil-Metriken (Grundlast, Spitzen, Anomalien, Sparpotenziale) + Ursachen-Signale (E-Heizung, PV-Eigenverbrauch, Dauerläufer) + signal-getriebene Rückfragen-Kandidaten aus einer Lastgang-Serie. IMMER als erster Analyse-Schritt nach list_load_series. lastprofil läuft auch auf Tageswerten weiter (Granularitäts-Caveat statt Verweigerung); signale verweigert bei Tageswerten (interval_minutes >= 60) — dann signale=null + signale_grund (Empfehlung, die Viertelstunden-Auslesung beim Netzbetreiber zu beantragen), lastprofil bleibt trotzdem verfügbar. Bei PV-Haushalten rechnet es automatisch mit dem Netzbezug statt mit dem Gesamtverbrauch (basis_label='Netzbezug'). Gespeicherte Fakten haben Vorrang vor Schätzungen: jeder Signal-Wert trägt eine quelle + profil_abgleich: quelle='profil'/'rechnung'/'messung' ist ein gespeicherter FAKT und IST die Antwort (z.B. aus submit_lastgang_facts) — quelle='heuristik' ist NUR eine Schätzung aus dem Lastprofil. Bei profil_abgleich.status='widerspruch' den Widerspruch dem User BENENNEN, nie stillschweigend zugunsten der Schätzung auflösen. mit_chartdaten=true liefert zusätzlich verdichtete Chart-Reihen (Stunden-Tagesprofil, Nachtlast je Tag) für Anzeige-Oberflächen — im Chat NICHT nötig, dort das Feld weglassen.
Eingabe
is_pvbooleantrue, falls der Haushalt eine PV-Anlage hat (dann wird mit dem Netzbezug gerechnet). mit_chartdatenbooleanoptional, Default false — für Anzeige-Oberflächen (z.B. die Gridbert-App), NICHT für den Chat. true liefert zusätzlich data.chartdaten: ein Stunden-Tagesprofil (gesamt + Werktag/Wochenende, je 24 Werte) und eine Nachtlastreihe je Tag (max. ~120 Tage) — verdichtete Anzeige-Aggregate, keine Rohserie. Ohne dieses Flag bleibt das Ergebnis unverändert. price_per_kwhnumberoptional, brutto EUR/kWh — für Kosten-Kontext (lastprofil) pv_feedin_kwhnumberoptional — Summe aus dem Einspeise-serie_ref, falls vorhanden. Ohne Angabe versucht der Gateway automatisch eine zweite aktive Einspeise-Freigabe desselben Accounts zu finden. serie_ref*stringaus list_load_series/request_data_release Ausgabe (oberste Ebene)
dataobjectZusammengeführtes Ergebnis (lastgang_analysis.run_analyze_load_profile). hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. metaobject | nullHerkunft der Rechnung/Daten aus energietools (quelle, stand, snapshot_version) — Inhalt variiert je Capability. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
answer_lastgang_questionGridbert-DomäneSpeichert die Antwort auf eine offene Lastgang-Rückfrage (ereignis_id aus list_open_lastgang_questions) — entweder label (EXAKT eines der optionen[].label dieser Frage) ODER freitext (frei formuliert, höchstens 200 Zeichen), NIE beides gleichzeitig erfinden: nur übernehmen, was der User wirklich gesagt hat. Liefert danach dieselbe Rückschau wie die Web-Antwortseite — was diese Auffälligkeit seit Beginn der Messwerte gemacht hat, längstens über die letzten zwölf Monate (Tage, kWh, wenn auflösbar Euro) —, damit die Antwort für den User einen sofortigen Gegenwert hat, nicht nur einen 'danke, notiert'. Eine leere rueckschau ist kein Fehler (z.B. ohne Datenfreigabe oder zu wenig Messwerte) — die Antwort ist trotzdem gespeichert. Im Berater-Workspace ist das der Weg, die telefonisch aufgenommene Antwort des Kunden einzupflegen; sie wird als Berater-Angabe vermerkt und dem Haushalt später nicht als seine eigene zurückzitiert. Auch dort gilt: nur übernehmen, was der Kunde wirklich gesagt hat.
Eingabe
ereignis_id*integeraus list_open_lastgang_questions, NICHT raten oder selbst vergeben. freitextstringFrei formulierte Antwort, falls keine Option passt. labelstringExakt eines der optionen[].label der Frage. Weglassen, wenn stattdessen freitext gesetzt wird. Ausgabe (oberste Ebene)
gespeichertbooleanimmer true — bei Ablehnung wird NICHTS gespeichert. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). rueckschauarraySätze der Rückschau in Alltagssprache (Tage, kWh, wenn auflösbar Euro). Kann LEER sein — kein Fehler, nur zu wenig Daten/keine Freigabe/kein Preis; die Antwort bleibt trotzdem gespeichert. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
capability_gapGridbert-DomäneMelde einen Bedarf, den Gridbert (noch) nicht abdecken kann, wenn für eine Energie-Frage des Nutzers kein passendes Werkzeug existiert. Das Team sieht gesammelt, was Nutzer sich wünschen, und baut danach weiter.
Eingabe
kategorie*stringBereich des fehlenden Bedarfs. kurzbeschreibung*stringWas hat konkret gefehlt? Zwei bis vier Sätze — welche Frage offen blieb, welche Zahl gebraucht wurde, ob sie extern beschafft werden musste. Keine Zählpunkte, Beträge oder Kontaktdaten. Ausgabe (oberste Ebene)
aufgenommenbooleanimmer true, wenn die Meldung angenommen wurde. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. inhaltstragendbooleanimmer true — der Freitext wird gespeichert (personenbezogene Angaben werden vorher entfernt). kategoriestringEingeordnete Kategorie. Eine unbekannte Kategorie fällt bewusst auf 'sonstiges' zurück, statt die Meldung zu verwerfen. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
check_invoice_readingGridbert-Domäne nur lesendLetzter Schritt des Lesens mit DEINEM Modell: prüft deine Antwort auf den Leseauftrag aus get_invoice_reading_task. Gridbert ruft hier KEIN Modell auf. Geprüft wird deterministisch: Schema, wörtliches Zitat gegen die Textebene, Kreuzproben (Summen, Verbrauch, Arbeitspreis), Zuschuss und Rabatt; danach wird mit dem Regelleser zusammengeführt (sicher gelesene Regel-Felder haben Vorrang, deine Felder stehen höchstens auf 'mittel'). Das Ergebnis hat EXAKT die Form von read_invoice_facts — je Feld Wert, Zitat und Konfidenz (hoch|mittel|niedrig|fehlt) — und ist dem User vorzulegen und bestätigen zu lassen, bevor submit_invoice_facts es rechnet ('quellen_anker' unverändert weitergeben). Rechnet nichts, speichert nichts. Eingabe: rechnung_ref, 'regelwerk_version' UNVERÄNDERT aus get_invoice_reading_task, und 'antwort' (dein JSON-Objekt in Kompaktform, oder derselbe Text; ein Codeblock drumherum wird toleriert; null, wenn lesen_noetig=false war). Fehler: error_class 'antwort_ungueltig' mit einer lesbaren Liste unter 'fehler' — korrigiere genau diese Stellen und rufe noch einmal auf (ein Nachbesserungsversuch genügt in der Regel); 'regelwerk_veraltet': das Regelwerk hat sich geändert, hol die Aufgabe mit get_invoice_reading_task neu. Gabst du bei get_invoice_reading_task seitentexte mit, gib dieselben hier wieder mit.
Eingabe
antwortobject | string | nullDeine Antwort auf den Leseauftrag: das JSON-Objekt in Kompaktform (oder ein Text mit genau diesem JSON). null, wenn get_invoice_reading_task lesen_noetig=false meldete. rechnung_ref*stringDieselbe Kennung wie bei get_invoice_reading_task. regelwerk_version*stringDie regelwerk_version aus get_invoice_reading_task, unverändert. seitentextearrayOptional, nur wenn schon bei get_invoice_reading_task angegeben: derselbe Text je Seite (Index 0 = Seite 1). Ausgabe (oberste Ebene)
ablehnungsgrundstring | nullteilbetrag | einspeisung | energiegemeinschaft | grosskunde | ausland | keine_energierechnung — oder null, wenn prüfbar. einmalrabatte_brutto_eurnumber | nullEinmaleffekte dieser Rechnung in €, brutto und positiv — oder null: befristete und einmalige Rabatte des Lieferanten (Neukunden-, Wechsel-, Aktionsbonus, Gratis-/Freimonate) × 1,2 plus ein Stromkostenzuschuss, der im Rechnungsbetrag schon abgezogen ist. Dauerhafte Nachlässe (Online, Bankeinzug, Bestandskunde) zählen nie, sie stecken im Arbeitspreis; ein Zuschuss ausserhalb des Betrags nie. Wiederkehrende Kosten der Periode = rechnungsbetrag_brutto_eur + einmalrabatte_brutto_eur. Gerechnet, nicht gelesen; NICHT an submit_invoice_facts weitergeben. energieartstringstrom | gas | kombi | unbekannt. energiepositionenarrayDie EINZELNEN Zeilen des Energieblocks, wenn die Rechnung dort mehr als eine führt (z.B. Spotpreis, Stromqualität, Mehrkosten Ökoenergie/HKN). Jeder Eintrag ist fertig für submit_invoice_facts: {bezeichnung, wert_eur, ist_netto}. **Unverändert weiterreichen, nichts ergänzen** — ein viertes Feld lässt die ganze Einreichung ablehnen. LEERE LISTE heisst 'keine erkannt', NICHT 'der Energieblock ist leer': dann wird der Block wie bisher über Arbeitspreis × Menge nachgerechnet. Die Belege dazu stehen in quellen_anker unter dem Feldnamen 'energiepositionen'. felderarrayEin Eintrag je gelesenem Feld. gelesen_mitstringtextebene = aus der Textebene des PDFs gelesen (nur dann kann ein Feld 'hoch' sein). ocr = aus einem Scan oder Foto bzw. aus Seitentexten des Aufrufers gelesen (höchstens 'mittel'). hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). pruefbarbooleanfalse = keine prüfbare Jahres-, Zwischen- oder Monatsrechnung. Dann steht der Grund in 'ablehnungsgrund' und es gibt keine Prüfung. quellen_ankerarrayFertig für die Übergabe an submit_invoice_facts: Objekte {feld, zitat}. NICHT neu bauen — dort wird geprüft, ob der übergebene Zahlenwert im Zitat wirklich vorkommt. rabatt_ausserhalb_bloecke_eurnumber | nullRabatt-/Bonuszeile AUSSERHALB der drei Blöcke in €, netto und negativ (Rabatt = Minuszahl) — oder null. Nur gesetzt, wenn die Rechnung damit aufgeht. Unverändert an submit_invoice_facts weitergeben: (Summe Blöcke + Rabatt) × 1,2 − Zuschuss = Rechnungsbetrag. rabatt_im_energieblock_eurnumber | nullSumme der gelesenen Rabatte des Lieferanten in €, netto und positiv — oder null. Unverändert an submit_invoice_facts weitergeben: die Nachrechnung zieht ihn nur ab, wenn der Energieblock danach aufgeht. rabattearrayDie Rabatt- und Bonuszeilen des Lieferanten hinter einmalrabatte_brutto_eur. Nur zur Anzeige; NICHT an submit_invoice_facts weitergeben. rechnungstypstringjahresrechnung | zwischenrechnung | monatsrechnung | unbekannt. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
compile_doneGedächtnis (engram)Meldet ein Inbox-Item als fertig eingearbeitet (nach compile_next + upsert_page). keys = die Seiten, in die der Inhalt eingearbeitet wurde; jede muss einen [quelle: <item_id>]-Anker tragen, dessen Werte wörtlich in der Quelle stehen — der Server prüft das automatisch. keys=[] heißt: nichts Verwertbares, nichts einzuarbeiten. Für jede Seite ohne Typ typing[key] = { type, status, relations, confidence } angeben, ausschließlich mit dem Vokabular aus dem tbox-Feld der letzten compile_next-Antwort. claim_token aus compile_next.item.claim_token UNVERÄNDERT zurückgeben — er beweist, dass das Item noch dir gehört (sonst lease_lost = die Sperre ist abgelaufen).
Eingabe
claim_tokenstringSperr-Schlüssel aus compile_next.item.claim_token — unverändert zurückgeben. item_id*stringDie Item-ID aus compile_next. keys*arraySeiten-Keys, in die eingearbeitet wurde; [] = nichts Verwertbares. typingobjectTyping je noch untypisierter Ziel-Seite: { "<key>": { "type": "<T-Box-Typ|null>", "status": "<status|null>", "relations": { "<prädikat>": ["<key>"] }, "confidence": 0..1 } }. Ausgabe (oberste Ebene)
hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). proxiedbooleanimmer true — die Nutzlast kommt aus der engram-Instanz, nicht aus dem Gateway. resultobject | nullRohes MCP-Tool-Result der Instanz: {content: [...], isError: bool}. Der eigentliche Inhalt steckt als JSON-Text in content[0].text. toolstringName des proxierten Vault-Tools. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
compile_nextGedächtnis (engram)Holt das nächste noch nicht eingearbeitete Item aus der Inbox und sperrt es für dich (nur eines pro Aufruf). Du bekommst dazu: eine Übersicht, in welche Seiten es gehört, die betroffenen Zielseiten, die Einarbeitungs-Regeln und das komplette Vokabular für die Typisierung (tbox.types/tbox.relations/tbox.statuses) — ein weiterer Tool-Call dafür ist nicht nötig. Leer = nichts zu tun.
Ausgabe (oberste Ebene)
hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). proxiedbooleanimmer true — die Nutzlast kommt aus der engram-Instanz, nicht aus dem Gateway. resultobject | nullRohes MCP-Tool-Result der Instanz: {content: [...], isError: bool}. Der eigentliche Inhalt steckt als JSON-Text in content[0].text. toolstringName des proxierten Vault-Tools. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
foerderung_und_beratungRechenwerk (energietools) nur lesendAuskunft zu Förderungen, kostenloser Energieberatung und Energiegemeinschaften in Österreich — in einem Aufruf. Nutze es, wenn der User nach Förderungen für PV, Speicher, Heizungstausch, Sanierung, Balkonkraftwerk, Geräte-Reparatur oder Wallbox fragt, eine unabhängige Energieberatung in seinem Bundesland sucht oder wissen will, was eine Energiegemeinschaft (GEA, EEG, BEG) bringt und wie man beitritt. Bietet es am besten NACH einem Tarifvergleich an, nicht davor. Braucht 'bundesland' ODER 'plz' (die PLZ wird intern aufgelöst; ist sie unbekannt oder liegt sie in zwei Bundesländern, kommt eine Rückfrage nach dem Bundesland statt eines geratenen Ergebnisses). Nur für Energiegemeinschaften reicht es auch ganz ohne Ort. 'thema' wählt einen Teil: 'foerderungen' (offene, verifizierte Förderungen von Bund und Land mit Fördersatz, Bedingungen, Antrags-Link und Quelle), 'energieberatung' (Beratungsstelle mit Träger und Kontakt) oder 'energiegemeinschaften' (Vorteile je Rechtsform, die Netzentgelt-Reduktion für BEG greift erst ab 31.12.2026 — NICHT schon ab 1.10.2026, das frühere Datum betrifft nur Begriffs-Definitionen; dazu Beitrittsschritte und bekannte Verzeichnis-Einträge, die NICHT vollständig sind). Ohne 'thema' kommen alle drei Teile. Geschlossene oder unsichere Einträge werden nie ausgespielt. Nenne dem User den Datenstand ('stand') je Teil; es ist KEINE Rechts- oder Förderberatung, vor einem Antrag immer die verlinkte Quelle prüfen. 'kategorien' filtert nur die Förderungen, z.B. ['pv','speicher'].
Eingabe
bundeslandstringBundesland, für das gesucht wird. kategorienarrayNur für Förderungen, z.B. ['pv','speicher']. Leer/weggelassen = alle Kategorien. plzstringPLZ, aus der das Bundesland aufgelöst wird (Alternative zu bundesland). themastringOptional: nur ein Teil. Weggelassen = Förderungen, Energieberatung und Energiegemeinschaften zusammen. Ausgabe (oberste Ebene)
bundeslandstringAufgelöstes Bundesland, falls eines bekannt ist. energieberatungobject | nullKostenlose, produktneutrale Energieberatungsstelle(n) des Bundeslands. energiegemeinschaftenobject | nullRechtsformen mit Netzentgelt-Vorteil, ElWG-Änderung, Beitrittsschritte, optional Verzeichnis-Einträge. ergebnissearrayImmer [] — Platzhalter des Leer-Vertrags. foerderungenobject | nullOffene, verifizierte Förderungen (Bund + Bundesland); geschlossene oder unsicher markierte Einträge sind ausgefiltert. grundstringWarum leer — z.B. 'keine Lastgang-Serien am Account'. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. leerbooleantrue = Leer-Vertrag (errors.empty_result): kein Fehler, aber auch keine Ergebnisse. Die Nutzdatenfelder dieses Tools fehlen dann. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). themastringfoerderungen | energieberatung | energiegemeinschaften | alles. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
get_data_release_statusGridbert-Domäne nur lesendFragt den Status einer Datenfreigabe ab (pending/active/revoked/…) + den aktuellen Datenstand der Serie. WARTE-HINWEIS ist PFLICHT: nach jeder Bestätigung im Netzbetreiber-Portal meldet der Netzbetreiber die Freigabe zurück — gemessen war die Hälfte nach rund fünf Stunden durch, einzelne brauchten mehrere Tage (Wartezeit beim Netzbetreiber, nicht dein Fehler). Sag das dem User ehrlich, versprich KEINE Minutenangabe und poll nicht endlos: Gridbert prüft stündlich und mailt von selbst, sobald sie da ist — und meldet sich auch, wenn eine Anfrage ungewöhnlich lange steht. Erinnere bei 'pending' an die ZWEITE, spätere Freigabe-Anfrage für Stammdaten (Adresse/Zählerdaten) — kein Fehler, wenn sie erst Stunden nach der ersten erscheint. Ohne zaehlpunkt liefert es den Status ALLER Freigaben des Accounts. GRANULARITÄT: sobald Daten fließen, sagen 'aufloesung_minuten' und 'q15_aktiv', wie fein sie ankommen. Ist 'q15_empfehlung' gesetzt, liefert der Zähler nur grobe Werte (z. B. Tagessummen) — sprich die Empfehlung dann AKTIV aus: die Viertelstundenauslesung ist ein Opt-in, das der User bei seinem Netzbetreiber anfordern kann, und ohne sie sind Lastgang-Tools (analyze_load_profile, spot_backtest) nicht möglich. Bei 'q15_aktiv': null ist die Auflösung UNBEKANNT (noch keine Daten) — dann nichts über die Granularität behaupten.
Eingabe
zaehlpunktstringOptional — ohne Angabe: alle Freigaben des Accounts Ausgabe (oberste Ebene)
ergebnissearrayImmer [] — Platzhalter des Leer-Vertrags. freigabenarrayEine Zeile je Freigabe. grundstringWarum leer — z.B. 'keine Lastgang-Serien am Account'. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. leerbooleantrue = Leer-Vertrag (errors.empty_result): kein Fehler, aber auch keine Ergebnisse. Die Nutzdatenfelder dieses Tools fehlen dann. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
get_invoice_reading_taskGridbert-Domäne nur lesendErster Schritt, wenn DU (dein Modell) die hochgeladene Rechnung lesen sollst, statt dass Gridbert ein eigenes Modell fragt. Gridbert ruft hier KEIN Modell auf: es lässt den deterministischen Regelleser laufen, bestimmt, welche Felder noch offen sind, und gibt dir den fertigen Leseauftrag — 'system' (Regelwerk + Ausgabeformat in Kompaktform), 'auftrag' (was zu lesen ist), 'dokument' und 'regelwerk_version'. ABLAUF in drei Schritten: (1) get_invoice_reading_task mit der rechnung_ref aus POST /accounts/rechnung; mit mit_dokument=true kommt die Rechnung (auf die nötigen Seiten gekürzt, ohne Metadaten) base64 im Ergebnis mit. (2) Lies das Dokument nach 'system' und 'auftrag' und antworte NUR mit dem JSON-Objekt in Kompaktform — wörtliche Zitate, nichts rechnen, nichts raten, Fehlendes weglassen. (3) check_invoice_reading mit rechnung_ref, der 'regelwerk_version' (unverändert) und deiner Antwort: es prüft deterministisch und liefert dasselbe Ergebnis wie read_invoice_facts, das du dem User zur Bestätigung vorlegst, bevor submit_invoice_facts es rechnet. lesen_noetig=false heisst: der Regelleser war sicher genug, du musst nichts lesen — dann check_invoice_reading mit antwort=null. Hat die Datei keine Textebene (Scan, Foto) und hast du eine eigene Texterkennung, gib ihren Text je Seite als seitentexte mit (Index 0 = Seite 1); Gridbert nutzt ihn für den Regelleser und die Zitatprüfung, vergibt auf fremdem Text aber höchstens Konfidenz 'mittel'. Rechnet nichts, speichert nichts.
Eingabe
mit_dokumentbooleantrue = das vorbereitete Dokument (PDF gekürzt bzw. Foto verkleinert, ohne Metadaten) base64 im Ergebnis mitliefern. Vorgabe false. rechnung_ref*stringKennung aus POST /accounts/rechnung. Nicht raten und nicht aus dem Gespräch rekonstruieren — sie ist eine Stunde gültig. seitentextearrayOptional: Text der Rechnung je Seite aus einer eigenen Texterkennung (Index 0 = Seite 1), für Dateien OHNE Textebene. Eine vorhandene Textebene geht vor. Ausgabe (oberste Ebene)
auftragstringAnweisung an das Modell (Nutzerteil), berücksichtigt offene_felder. Leer bei lesen_noetig=false. dokumentobjectDas Dokument, wie ein Modell es bekommen sollte: PDF auf die relevanten Seiten gekürzt und ohne Metadaten, Foto neu kodiert und ohne EXIF. empfehlungobjectEmpfohlene Einstellung für das lesende Modell. Eine Empfehlung: das Regelwerk ist an diesem Modell gemessen, die Prüfung hängt nicht daran. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. lesen_noetigbooleanfalse = der Regelleser hat alles sicher gelesen, es muss kein Modell lesen: check_invoice_reading mit antwort=null aufrufen. offene_felderarray | nullFelder, die der Regelleser nicht sicher kennt und die das Modell lesen soll. null = alle Felder (kein Regelleser-Ergebnis, z.B. bei Scans). okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). regelleserobject | nullVorläufiger Stand des deterministischen Regellesers (Feld, Wert, Konfidenz, Zitat) — null, wenn er nichts lesen konnte (Scan, Foto). regelwerk_versionstringVersion des Regelwerks, nach dem zu lesen ist. UNVERÄNDERT an check_invoice_reading zurückgeben. systemstringRegelwerk und Ausgabeformat (Kompaktform) als System-Text für das lesende Modell. Leer bei lesen_noetig=false. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
get_knowledgeRechenwerk (energietools) nur lesendDeterministische Auslieferung der Wissensschicht (wie sich AT-Stromkosten zusammensetzen o. ä.). Reine Text-Auslieferung, kein Rechen-Result. 'thema' ist ein fester Wiki-Slug — Kern-Themen: 'stromkosten-zusammensetzung', 'netz-netzentgelte', 'tarife', 'steuern', 'foerderung', 'glossar'. Bei unbekanntem Thema listet die Fehlermeldung die gültigen Slugs auf.
Eingabe
thema*stringWiki-Slug, z.B. 'stromkosten-zusammensetzung' (mit Bindestrich, nicht Unterstrich) Ausgabe (oberste Ebene)
dataobjectWissens-Artikel der Capability 'get_knowledge'. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. metaobject | nullHerkunft der Rechnung/Daten aus energietools (quelle, stand, snapshot_version) — Inhalt variiert je Capability. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
get_pageGedächtnis (engram) nur lesendLiefert eine gespeicherte Seite im Volltext. Den Seiten-Key (Slug) bekommst du aus search_pages (Feld key) oder aus dem Ergebnis von compile_next/upsert_page; rate keinen Key.
Eingabe
key*stringSeiten-Key (Slug), wie er in search_pages-Treffern steht. Ausgabe (oberste Ebene)
hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). proxiedbooleanimmer true — die Nutzlast kommt aus der engram-Instanz, nicht aus dem Gateway. resultobject | nullRohes MCP-Tool-Result der Instanz: {content: [...], isError: bool}. Der eigentliche Inhalt steckt als JSON-Text in content[0].text. toolstringName des proxierten Vault-Tools. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
get_switch_infoGridbert-Domäne nur lesendWechsel-Infos (Schritte, Fristen, Kontaktweg) aus Katalogdaten. Reine Information — KEIN Vollmacht-Flow, keine Durchführung.
Eingabe
lieferantstringName des Ziel-Lieferanten, optional. Ausgabe (oberste Ebene)
fristenobjectRücktrittsrecht, Kündigungsfrist, übliche Wechseldauer. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. hinweis_vollmachtstringKlarstellung: Gridbert wechselt NICHT selbst. kontakt_hinweisstringNur mit Lieferant: wo der Kontaktweg steht. lieferantstringNur, wenn ein Lieferant übergeben wurde. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). schrittearrayDie generischen AT-Wechselschritte. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
ingestGedächtnis (engram)Legt neue Rohdaten (JSON oder Text, z.B. eine genannte Tatsache) im Eingangsspeicher der Instanz ab. Sie werden nie überschrieben, nur ergänzt. Eingearbeitet werden sie danach über compile_next.
Eingabe
content*stringDer abzulegende Inhalt (JSON oder Text). content_typestringMIME-Type des Inhalts (Default 'application/json'). sourcestringHerkunft der Daten (frei wählbare Kennung). Ausgabe (oberste Ebene)
hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). proxiedbooleanimmer true — die Nutzlast kommt aus der engram-Instanz, nicht aus dem Gateway. resultobject | nullRohes MCP-Tool-Result der Instanz: {content: [...], isError: bool}. Der eigentliche Inhalt steckt als JSON-Text in content[0].text. toolstringName des proxierten Vault-Tools. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
lastgang_heuteRechenwerk (energietools) nur lesendNormalband je Viertelstunde (P25/Median/P75 über die letzten 4 Wochen, DESSELBEN Tagtyps — Werktag/Samstag/Sonn-und-Feiertag) + drei Tagesaggregate mit Vergleichswert: Energie heute bisher, Tagesspitze, Monat bisher — jedes als Ist/Normal/Delta-Prozent. 'referenz' nennt IMMER Tagtyp, Fenster und Anzahl der Referenztage — ohne sie ist ein Vergleichswert eine Behauptung, nie unkommentiert lassen. Reicht die Datenlage für ein Aggregat nicht (z.B. Messlücke, zu wenige Referenztage vom selben Tagtyp), ist der jeweilige Normalwert null und der zugehörige '..._grund' erklärt warum — NIE einen Ersatzwert erfinden oder verschweigen, dass er fehlt. 'heute bisher'/'Tagesspitze' vergleichen bewusst NUR bis zur aktuellen Uhrzeit (nicht den ganzen Tag) — der Rest des Tages ist noch nicht gemessen. Braucht eine 15-min-Serie; bei reinen Tageswerten liefert 'referenz' ausreichend=false mit Grund statt eines Fehlers.
Eingabe
serie_ref*stringaus list_load_series/request_data_release Ausgabe (oberste Ebene)
dataobjectNormalband-Ergebnis (energietools.capabilities.lastgang.normalband.NormalbandCompute, unverändert durchgereicht). hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
list_load_seriesGridbert-Domäne nur lesendListet alle Lastgang-Serien am Account (Zählpunkt MASKIERT, Richtung, Zeitraum, Coverage, Granularität, Datenstand). IMMER als ERSTER Schritt aufrufen: bevor eine neue Datenfreigabe angefordert wird (keine doppelte Freigabe-Anfrage) und bevor ein Analyse-Tool (analyze_load_profile/load_trend/spot_backtest) aufgerufen wird — liefert den serie_ref, den die Analyse-Tools brauchen.
Ausgabe (oberste Ebene)
ergebnissearrayImmer [] — Platzhalter des Leer-Vertrags. grundstringWarum leer — z.B. 'keine Lastgang-Serien am Account'. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. leerbooleantrue = Leer-Vertrag (errors.empty_result): kein Fehler, aber auch keine Ergebnisse. Die Nutzdatenfelder dieses Tools fehlen dann. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). serienarrayEine Zeile je Serie. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
list_open_lastgang_questionsGridbert-Domäne nur lesendListet die offenen Rückfragen dieses Kontos zu Lastgang-Auffälligkeiten (z.B. 'was lief da nachts?') — Frage, Optionen und Zeitraum je Ereignis, damit du die Frage im Chat stellen kannst. 'Offen' heißt HIER: noch nicht beantwortet, egal ob schon per Mail verschickt — ein Konto, das im Chat von einem Gerät erzählt, muss dieselbe Frage nicht nochmal per Mail bekommen. Kein Input: das Konto kommt immer aus der Anmeldung, nie als Parameter. Rufe danach answer_lastgang_question mit der ereignis_id auf, sobald der User geantwortet hat.
Ausgabe (oberste Ebene)
ergebnissearrayImmer [] — Platzhalter des Leer-Vertrags. fragenarrayEine Zeile je offener Frage. grundstringWarum leer — z.B. 'keine Lastgang-Serien am Account'. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. leerbooleantrue = Leer-Vertrag (errors.empty_result): kein Fehler, aber auch keine Ergebnisse. Die Nutzdatenfelder dieses Tools fehlen dann. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
load_trendRechenwerk (energietools) nur lesendMehrjahres-Trend (YoY) + Treiber-Zerlegung nach Leistungsband x Tageszeit x Werktag/Wochenende aus einer Lastgang-Serie. trend: Vorjahresvergleich je Kalenderjahr nur bei >=2 vollen Kalenderjahren — sonst Vorjahresvergleich über deckungsgleiche Zeitfenster (Monat, Tag, Stunde, Minute); NIE ein Teiljahr gegen ein Volljahr stellen; läuft auch auf Tageswerten weiter (Granularitäts-Caveat). attribution benennt je Treiber eine Geräte-KLASSE als Hypothese — NIE einen Gerätenamen (Viertelstundenwerte reichen dafür nicht aus) — nur mit >=2 Jahren Viertelstunden-Daten verfügbar, sonst attribution=null + attribution_grund (trend bleibt trotzdem verfügbar).
Eingabe
jahr_aintegerattribution-Basisjahr, leer = zweitjüngstes Jahr der Serie jahr_bintegerattribution-Vergleichsjahr, leer = jüngstes Jahr der Serie serie_ref*stringaus list_load_series/request_data_release Ausgabe (oberste Ebene)
dataobjectZusammengeführtes Ergebnis (lastgang_analysis.run_load_trend). hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. metaobject | nullHerkunft der Rechnung/Daten aus energietools (quelle, stand, snapshot_version) — Inhalt variiert je Capability. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
pv_potenzialRechenwerk (energietools) nur lesendNUR bei vorhandener Lastgang-Serie (serie_ref aus list_load_series) mit Viertelstundenwerten. Beantwortet 'Was würde mir eine PV-Anlage bringen?' auf dem ECHTEN Verbrauchsverlauf statt auf einem Standardprofil: je Anlagengröße Ertrag, Eigenverbrauch, Eigenverbrauchsquote, Autarkie, Einspeisung, vermiedene Netzkosten und Amortisation. Das Sonnenertragsprofil des Standorts holt der Gateway (PVGIS) — NIE vom User-LLM. Verweigert bei Tageswerten mit Begründung, weil sich daraus die Tageszeit des Verbrauchs nicht ablesen lässt. Liefert zwei getrennte Ergebnisse: 'gemessen' (nichts modelliert) und 'jahr_modelliert' (Tagessummen + gemessene Tagesform, als Modell gekennzeichnet). Euro-Beträge gibt es nur auf Jahresbasis — ein Teiljahr wird NICHT hochgerechnet. Die Einspeisevergütung ist eine Annahme und wird als Szenario-Band gerechnet.
Eingabe
arbeitspreis_netto_ct_kwhnumberArbeitspreis netto in ct/kWh (Alternative zu energiepreis_brutto_ct_kwh). ausrichtungstringSüd, Südost, Südwest, Ost, West (Default Süd) diskontratenumberDiskontrate für die Kapitalwertrechnung (z.B. 0.04 = 4%). einspeise_szenarien_ct_kwharrayAnnahme-Band, Default [3, 5, 8] netto ct/kWh energiepreis_brutto_ct_kwhnumberArbeitspreis von der Rechnung (brutto) gemeindestringGemeinde des Anschlusses. Nur nötig, wenn die Antwort danach fragt: bei geteilten PLZ (z.B. 1140, 1210) liegen zwei Netzgebiete nebeneinander. gemessene_anlage_kwpnumberInstallierte Leistung der BESTEHENDEN Anlage in kWp, zu der gemessener_jahresertrag_kwh gehört. Gridbert rechnet daraus den Ertrag je kWp. Nicht mit kwp_varianten verwechseln — das sind die durchzurechnenden Größen, das hier ist die reale. gemessener_jahresertrag_kwhnumberNUR bei einer BESTEHENDEN Anlage: tatsächlich gemessener Jahresertrag in kWh (vom Wechselrichter/Zähler). Ersetzt den PVGIS-Modellertrag — sinnvoll bei Verschattung, Mischausrichtung oder Balkonkraftwerken, wo das Modell weit danebenliegt. Nur zusammen mit gemessene_anlage_kwp verwenden; beide Zahlen erfragen, nie schätzen. investition_eur_pro_kwpobjectInvestition brutto EUR/kWp je Größe, z.B. {"10": 1400} kwp_variantenarrayAnlagengrößen in kWp, Default [5, 10] neigung_gradintegerDefault 35 nutzungsdauer_jahreintegerNutzungsdauer der Anlage in Jahren für die Amortisationsrechnung. plz*stringPLZ des Standorts, für das Sonnenertragsprofil (PVGIS). serie_ref*stringaus list_load_series/request_data_release speicher_kwhnumberZusatzvariante mit Speicher (0 = ohne) Ausgabe (oberste Ebene)
dataobjectErgebnis der Capability 'pv_potenzial' (+ gateway-seitiger Standort). hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. metaobject | nullHerkunft der Rechnung/Daten aus energietools (quelle, stand, snapshot_version) — Inhalt variiert je Capability. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
read_invoice_factsGridbert-Domäne nur lesendLiest aus einer hochgeladenen Rechnung (rechnung_ref aus POST /accounts/rechnung) die Felder, die submit_invoice_facts braucht — je Feld mit wörtlichem Zitat von der Rechnung und einer Konfidenz (hoch|mittel|niedrig|fehlt). Rechnet NICHTS und speichert NICHTS: die gelesenen Werte gehören dem User vorgelegt und erst nach seiner Bestätigung an submit_invoice_facts übergeben. Ein Feld mit konfidenz='niedrig' oder 'mittel' ist zu bestätigen, eines mit 'fehlt' zu erfragen — nie selbst ergänzen und nie aus dem Gesprächsverlauf raten. 'mittel' heisst zusätzlich: der Wert stammt aus einer gescannten Seite. Wer nur drei Stufen kennt, darf den vierten Wert NICHT wegwerfen — er ist da und er ist vorzulegen. Beträge und Mengen kommen SYSTEMATISCH als 'niedrig' zurück; das ist kein Mangel, sondern die gemessene Erkennungsgüte (Verbrauch 86 %, Rechnungsbetrag 90 %) — sie sind ausnahmslos vorzulegen. pruefbar=false heisst, die Rechnung ist keine prüfbare Jahres-, Zwischen- oder Monatsrechnung (Teilbetragsvorschreibung, Einspeisung, Energiegemeinschaft, Grosskunde über 100.000 kWh, ausländische Rechnung, keine Energierechnung): dann 'ablehnungsgrund' dem User im Klartext sagen und KEINE Prüfung anbieten. 'quellen_anker' ist fertig für die Übergabe an submit_invoice_facts aufgebaut, nicht neu bauen. Fotos, Scans und PDFs ohne Textebene werden gelesen (gelesen_mit='ocr', Werte daraus höchstens 'mittel'); not_configured kommt nur, wenn auf diesem Server gar kein Leseweg dafür eingerichtet ist. Dieses Werkzeug ruft Gridberts eigenes Modell auf; wer ein eigenes Modell mitbringt, nimmt stattdessen get_invoice_reading_task und check_invoice_reading.
Eingabe
leserstringvollstaendig (Vorgabe): der ganze Lesevorgang. schnell: nur die Textebene, in Sekunden — für eine erste Anzeige; bei einem Scan kommt ein leerer Befund mit gelesen_mit='ausstehend'. Danach vollstaendig aufrufen. rechnung_ref*stringKennung aus POST /accounts/rechnung. Nicht raten und nicht aus dem Gespräch rekonstruieren — sie ist eine Stunde gültig. Ausgabe (oberste Ebene)
ablehnungsgrundstring | nullteilbetrag | einspeisung | energiegemeinschaft | grosskunde | ausland | keine_energierechnung — oder null, wenn prüfbar. einmalrabatte_brutto_eurnumber | nullEinmaleffekte dieser Rechnung in €, brutto und positiv — oder null: befristete und einmalige Rabatte des Lieferanten (Neukunden-, Wechsel-, Aktionsbonus, Gratis-/Freimonate) × 1,2 plus ein Stromkostenzuschuss, der im Rechnungsbetrag schon abgezogen ist. Dauerhafte Nachlässe (Online, Bankeinzug, Bestandskunde) zählen nie, sie stecken im Arbeitspreis; ein Zuschuss ausserhalb des Betrags nie. Wiederkehrende Kosten der Periode = rechnungsbetrag_brutto_eur + einmalrabatte_brutto_eur. Gerechnet, nicht gelesen; NICHT an submit_invoice_facts weitergeben. energieartstringstrom | gas | kombi | unbekannt. energiepositionenarrayDie EINZELNEN Zeilen des Energieblocks, wenn die Rechnung dort mehr als eine führt (z.B. Spotpreis, Stromqualität, Mehrkosten Ökoenergie/HKN). Jeder Eintrag ist fertig für submit_invoice_facts: {bezeichnung, wert_eur, ist_netto}. **Unverändert weiterreichen, nichts ergänzen** — ein viertes Feld lässt die ganze Einreichung ablehnen. LEERE LISTE heisst 'keine erkannt', NICHT 'der Energieblock ist leer': dann wird der Block wie bisher über Arbeitspreis × Menge nachgerechnet. Die Belege dazu stehen in quellen_anker unter dem Feldnamen 'energiepositionen'. felderarrayEin Eintrag je gelesenem Feld. gelesen_mitstringtextebene = aus der Textebene des PDFs gelesen (nur dann kann ein Feld 'hoch' sein). ocr = aus einem Scan oder Foto bzw. aus Seitentexten des Aufrufers gelesen (höchstens 'mittel'). hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). pruefbarbooleanfalse = keine prüfbare Jahres-, Zwischen- oder Monatsrechnung. Dann steht der Grund in 'ablehnungsgrund' und es gibt keine Prüfung. quellen_ankerarrayFertig für die Übergabe an submit_invoice_facts: Objekte {feld, zitat}. NICHT neu bauen — dort wird geprüft, ob der übergebene Zahlenwert im Zitat wirklich vorkommt. rabatt_ausserhalb_bloecke_eurnumber | nullRabatt-/Bonuszeile AUSSERHALB der drei Blöcke in €, netto und negativ (Rabatt = Minuszahl) — oder null. Nur gesetzt, wenn die Rechnung damit aufgeht. Unverändert an submit_invoice_facts weitergeben: (Summe Blöcke + Rabatt) × 1,2 − Zuschuss = Rechnungsbetrag. rabatt_im_energieblock_eurnumber | nullSumme der gelesenen Rabatte des Lieferanten in €, netto und positiv — oder null. Unverändert an submit_invoice_facts weitergeben: die Nachrechnung zieht ihn nur ab, wenn der Energieblock danach aufgeht. rabattearrayDie Rabatt- und Bonuszeilen des Lieferanten hinter einmalrabatte_brutto_eur. Nur zur Anzeige; NICHT an submit_invoice_facts weitergeben. rechnungstypstringjahresrechnung | zwischenrechnung | monatsrechnung | unbekannt. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
request_data_releaseGridbert-DomäneFordert die Datenfreigabe für einen Zählpunkt an (der Kunde gibt dem Netzbetreiber seine Zustimmung zur Datenweitergabe an Gridbert). Der Zählpunkt MUSS aus den Invoice-Fakten der Instanz stammen (get_page/search_pages) — nicht vom User abtippen lassen. Nur aufrufen, wenn list_load_series noch keine aktive Freigabe für diesen Zählpunkt zeigt (keine doppelte Freigabe-Anfrage). energy_direction ist PFLICHT (consumption|generation) — bewusst setzen, NICHT raten: eine falsche Richtung blockiert den Zählpunkt beim Netzbetreiber (die Anfrage bleibt lange auf 'pending', jeder weitere Versuch endet mit einem Fehler). Ist die Richtung nicht klar aus den Invoice-Fakten ableitbar, den User zuerst fragen: 'Hast du eine PV-Anlage mit Einspeisung an diesem Zählpunkt?' Kündige dem User EXAKT an: 'Du bekommst zwei Freigabe-Anfragen im Netzbetreiber-Portal unter Freigaben→Offen (Provider EP100505) — zuerst die Energiedaten, wenige Stunden später eine zweite für Stammdaten (Adresse/Zählerdaten).' Beide sind eigene Freigaben mit eigenem Ablauf — die zweite (Stammdaten) schickt der Netzbetreiber automatisch, du musst sie nicht selbst auslösen, aber im Portal ebenfalls bestätigen. Nach jeder Bestätigung meldet der Netzbetreiber die Freigabe zurück; wie lange das dauert, ist sehr verschieden — gemessen war die Hälfte nach rund fünf Stunden durch, einzelne brauchten mehrere Tage (ehrlich sagen, keine Live-Fortschrittsanzeige, keine Minutenangabe versprechen). Gridbert prüft stündlich und mailt von selbst, sobald sie da ist. Bei bestätigter PV-Einspeisung: ZWEITER Aufruf mit energy_direction='generation' und dem Einspeise-Zählpunkt (eigene Nummer, i.d.R. Suffix …9901).
Eingabe
energy_direction*stringPFLICHT, bewusst setzen: 'consumption' für Bezug, 'generation' NUR für den separaten Einspeise-Zählpunkt bei PV. Nicht ableitbar? Erst den User fragen, nicht raten. plz*stringPLZ des Zählpunkts (4-stellig). zaehlpunkt*string33-stelliger AT-Zählpunkt aus den Invoice-Fakten (kein Abtippen durch den User) Ausgabe (oberste Ebene)
bereits_freigegebenbooleanNur im Bestandsfall: true = es lief schon eine Freigabe, kein zweite Freigabe-Anfrage. energy_directionstringNur bei frischer Anforderung: 'consumption' oder 'generation'. esp_providerstringNur bei frischer Anforderung: Provider-Kennung im Portal. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). portal_pfadstringNur bei frischer Anforderung: 'Freigaben → Offen'. serie_refstringReferenz auf die Serie — der Schlüssel für alle Lastgang-Tools. statusstringFreigabe-Status: 'pending' nach frischer Anforderung, sonst der Stand der bestehenden Freigabe ('pending'|'active'). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. zaehlpunkt_maskedstringZählpunkt MASKIERT. Der Klartext verlässt den Gateway nie. -
search_pagesGedächtnis (engram) nur lesendVolltextsuche über die gespeicherten Seiten der Instanz (reine Textsuche, ohne KI-Auswertung) — das GEDÄCHTNIS des Haushalts. Hier liegen aus Rechnungen gemerkte Fakten (Jahresverbrauch, Arbeits-/Grundpreis, Lieferant, Zeitraum) UND selbst genannte Haushalts-Fakten (Heizart, Geräte, PV, …). ZUERST aufrufen bei JEDER Frage zu dem, was der User schon eingebracht hat — Verbrauch, Kosten, Tarif, Lieferant, WIE er heizt —, BEVOR du aus einem Lastprofil-Signal oder einer Schätzung etwas ableitest, nach manuellen Daten fragst oder 'weiß ich nicht' sagst. Ein gespeicherter Fakt gilt immer mehr als eine Schätzung. Suchtext als 'query'; 'limit' optional (Default 10). Die Treffer enthalten den key, den get_page braucht.
Eingabe
limitintegerMaximale Trefferzahl (Default 10). query*stringSuchtext (Stichworte oder eine Frage). Ausgabe (oberste Ebene)
hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). proxiedbooleanimmer true — die Nutzlast kommt aus der engram-Instanz, nicht aus dem Gateway. resultobject | nullRohes MCP-Tool-Result der Instanz: {content: [...], isError: bool}. Der eigentliche Inhalt steckt als JSON-Text in content[0].text. toolstringName des proxierten Vault-Tools. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
speicher_dimensionierungRechenwerk (energietools) nur lesendNUR bei vorhandener Lastgang-Serie in Viertelstunden-Auflösung. Beantwortet 'Wie groß soll der Batteriespeicher sein?': je Größe Eigenverbrauchsquote, Autarkie, Vollzyklen, Amortisation, Kapitalwert — und den Grenznutzen, also ab welcher Größe die nächste Kilowattstunde fast nichts mehr bringt. Sagt auch ehrlich Nein, wenn keine Größe einen positiven Kapitalwert erreicht. Speicher-Alterung ist NICHT eingepreist; die Vollzyklen je Größe stehen im Ergebnis. Braucht die Anlagengröße (kwp).
Eingabe
arbeitspreis_netto_ct_kwhnumberArbeitspreis netto in ct/kWh (Alternative zu energiepreis_brutto_ct_kwh). ausrichtungstringSüd, Südost, Südwest, Ost, West (Default Süd). diskontratenumberDefault 0.04 einspeise_netto_ct_kwhnumberDefault 5 energiepreis_brutto_ct_kwhnumberArbeitspreis brutto in ct/kWh. gemeindestringGemeinde des Anschlusses. Nur nötig, wenn die Antwort danach fragt: bei geteilten PLZ (z.B. 1140, 1210) liegen zwei Netzgebiete nebeneinander. gemessene_anlage_kwpnumberInstallierte Leistung in kWp, zu der der gemessene Jahresertrag gehört. Meist dieselbe Zahl wie kwp. gemessener_jahresertrag_kwhnumberNUR bei einer BESTEHENDEN Anlage: tatsächlich gemessener Jahresertrag in kWh. Ersetzt den PVGIS-Modellertrag — bei Verschattung oder Mischausrichtung liegt das Modell weit daneben, und die Speichergröße hängt daran. Nur zusammen mit gemessene_anlage_kwp; beide erfragen, nie schätzen. groessen_kwharraySpeichergrößen, Default [2.5, 5, 7.5, 10, 15] kwp*numberAnlagengröße in kWp neigung_gradintegerDachneigung in Grad (Default 35). nutzungsdauer_jahreintegerDefault 15 plz*stringPLZ des Lieferorts (4-stellig). serie_ref*stringaus list_load_series/request_data_release speicher_kosten_eur_pro_kwhnumberDefault 600 Ausgabe (oberste Ebene)
dataobjectErgebnis der Capability 'speicher_dimensionierung' (+ Standort). hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. metaobject | nullHerkunft der Rechnung/Daten aus energietools (quelle, stand, snapshot_version) — Inhalt variiert je Capability. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
spot_backtestRechenwerk (energietools) nur lesendNUR bei vorhandener Lastgang-Serie (serie_ref aus list_load_series). Für den normalen Tarifvergleich aus einer Rechnung ist dieses Tool NICHT nötig — dafür tariff_compare, das ganz ohne Lastgang rechnet. Profilgewichteter Spot-Backtest (echter Verbrauchs-Shape x EPEX-Stundenpreise, aus Gridberts eigener Datenbank — NIE vom User-LLM übergeben) vs. aktueller Fixpreis, plus Tarifwechsel-Ersparnis. tarif_ersparnis ist nur verfügbar, wenn plz/energiepreis_brutto_ct_kwh/aktuelle_grundgebuehr_brutto_eur_monat/aktueller_lieferant/jahresverbrauch_kwh vollständig sind — sonst verfuegbar=false + grund (nie eine stille 0). Hebel-Reihenfolge: Tarif zuerst prüfen. Verweigert bei Tageswerten mit Begründung.
Eingabe
aktuelle_grundgebuehr_brutto_eur_monatnumberGrundgebühr brutto in €/Monat. aktueller_lieferantstringName des aktuellen Stromlieferanten. aufschlag_ctnumberAnnahme (Lieferanten-Aufschlag), Default aus energietools energiepreis_brutto_ct_kwhnumberArbeitspreis brutto in ct/kWh. jahresverbrauch_kwhnumberJahresverbrauch in kWh. plzstringPLZ des Lieferorts (4-stellig). serie_ref*stringaus list_load_series/request_data_release Ausgabe (oberste Ebene)
dataobjectErgebnis der Capability 'spot_backtest'. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. metaobject | nullHerkunft der Rechnung/Daten aus energietools (quelle, stand, snapshot_version) — Inhalt variiert je Capability. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
submit_invoice_factsGridbert-DomäneÜbergibt EXAKT abgelesene Rechnungswerte + wörtliche Zitate. Validiert deterministisch (energieart muss strom|gas|kombi sein — Nicht-Energie-Rechnungen z.B. Restaurant werden mit error_class=validation_rejected und benanntem Grund abgelehnt, nichts wird gespeichert), rechnet finalize, schreibt die Fakten-Submission in den Raw-Store. PFLICHT: energieart, lieferant, plz, verbrauch_kwh, zeitraum_von, zeitraum_bis (ISO YYYY-MM-DD oder DE TT.MM.JJJJ) und quellen_anker. Zusätzlich muss arbeitspreis (ct/kWh) ODER summe_energieentgelte vorliegen. quellen_anker sind Objekte {feld, zitat} — je ein wörtliches Zitat für den Verbrauch und einen Betrag, z.B. quellen_anker=[{"feld":"verbrauch_kwh","zitat":"Jahresverbrauch 2562,3 kWh"},{"feld":"arbeitspreis","zitat":"Arbeitspreis 16,02 ct/kWh"}]. Lehnt bei Unplausibilität feld-genau mit Rückfrage ab (nichts wird gespeichert). Dieses Tool speichert nur — die Frage 'zahle ich zu viel?' beantwortet erst tariff_compare, das danach im selben Zug zu rechnen ist.
Eingabe
arbeitspreisobjectArbeitspreis: {wert_ct_kwh, ist_netto}. Erforderlich, falls keine summe_energieentgelte. energieart*stringEnergieart der Rechnung: 'strom', 'gas' oder 'kombi'. energiepositionenarray | nullDie EINZELNEN Positionen des Energieblocks, wenn die Rechnung dort mehr als eine Zeile führt (z.B. Spotpreis, Stromqualität, Mehrkosten Ökoenergie/HKN). Optional — ohne sie wird der Energieblock als Arbeitspreis × Menge nachgerechnet, was bei einem mehrteiligen Block einen Fehlbefund ergibt. read_invoice_facts liefert sie fertig; unverändert weiterreichen, nichts ergänzen. **Die Grundpreis-/Grundgebühr-Zeile gehört NICHT hier hinein**, wenn sie zusätzlich unter 'grundgebuehr' übergeben wird: sie würde zweimal gerechnet, und der Energieblock sähe um die Gebühr zu günstig aus. Im Zweifel die Positionen ganz weglassen. grundgebuehrobjectGrundgebühr: {wert_eur, zeitraum(monat|jahr), ist_netto} plus optional block und aliquotierung. jahresverbrauch_prognose_kwhnumberDer von der Rechnung PROGNOSTIZIERTE Jahresverbrauch in kWh, falls ausgewiesen — nicht der Verbrauch des Abrechnungszeitraums (der steht in verbrauch_kwh). lieferant*stringName des Lieferanten auf der Rechnung. plz*stringPLZ des Lieferorts (4-stellig). quellen_anker*arrayWörtliche Belegstellen je Feld — mindestens ein Zitat für Verbrauch und einen Betrag. rabatt_ausserhalb_bloecke_eurnumber | nullRabatt-/Bonuszeile außerhalb der drei Blöcke, netto in € und negativ (Rabatt = Minuszahl) — so wie read_invoice_facts sie liefert, unverändert weitergeben. Die Nachrechnung rechnet (Summe Blöcke + Rabatt) × 1,2 − Zuschuss. rabatt_im_energieblock_eurnumber | nullRabatt des Lieferanten, netto und positiv in € — so wie read_invoice_facts ihn liefert, unverändert weitergeben. Die Nachrechnung zieht ihn nur ab, wenn der Energieblock danach aufgeht (z. B. Online-Bonus als Abzugszeile). rechnungsbetrag_brutto_eurnumberDer ausgewiesene Rechnungsbetrag brutto in €. Ohne ihn gibt es keine Summenprobe (Energie + Netz + Abgaben gegen den Betrag). summe_energieentgelteobjectSumme des Energieentgelt-Blocks in € (Zeile 'Energiekosten'/'Summe Energieentgelte'): {wert_eur, ist_netto}. Erforderlich, falls kein arbeitspreis — z.B. wenn nur eine Blocksumme auf der Rechnung steht, kein ct/kWh-Wert. summe_netzentgelteobjectSumme des Netzentgelt-Blocks in € (Zeile 'Summe Netzentgelte'/'Summe Netz'): {wert_eur, ist_netto}. summe_steuern_abgabenobjectSumme des Abgabenblocks in €: {wert_eur, ist_netto}. Die Zeile heißt je nach Versorger 'Summe Steuern und Abgaben', 'Summe Abgaben/Zuschläge' oder 'Summe Steuern, Abgaben und Zuschläge' — gemeint ist immer die Blocksumme, nicht eine einzelne Abgabe. Führt die Rechnung MEHRERE Blöcke neben Energie und Netz (z.B. zusätzlich 'Summe Sonstige'), gehört deren Summe hier zusammengefasst herein, sonst geht die Gesamtsumme nicht auf. Ohne sie ist die Rechnung nicht nachrechenbar. tarif_namestringProduktname des Tarifs, wie er auf der Rechnung steht. verbrauch_kwh*numberVerbrauch im Abrechnungszeitraum in kWh. zaehlpunktstringZählpunkt von der Rechnung (33 Zeichen, AT + 31 Ziffern; Punkte und Leerzeichen egal). Optional — aber wenn er auf der Rechnung steht, mitgeben: er benennt das Netzgebiet, das sonst aus der PLZ erschlossen wird, und die liegt falsch, wo ein Netzbetreiber über die Bundeslandgrenze versorgt. Liegt eine Datenfreigabe vor, ergänzt der Gateway ihn selbst. zeitraum_bis*stringAbrechnungsende, ISO YYYY-MM-DD oder DE TT.MM.JJJJ zeitraum_von*stringAbrechnungsbeginn, ISO YYYY-MM-DD oder DE TT.MM.JJJJ zuschuss_im_betrag_eurnumberUmsatzsteuerfreier Zuschuss, der im Rechnungsbetrag bereits abgezogen ist (z. B. Stromkostenzuschuss 2022–2024), positiv in €. Nur setzen, wenn er als Abzugszeile auf der Rechnung steht — die Summenprobe rechnet ihn zurück. Ausgabe (oberste Ebene)
gespeichertbooleanimmer true — bei Ablehnung wird NICHTS gespeichert. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). rechnungobject | nullErgebnis der Capability 'finalize_invoice' — der Rechenweg, aus dem die Eingaben für tariff_compare stammen (nicht erneut beim User erfragen). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
submit_lastgang_factsGridbert-DomäneSpeichert Profil-Fakten zum Haushalt (Heizung, PV-Eckdaten, Dauerläufer, Lastverschiebung, Heizstromtarif, Viertelstunden-Auslesung beantragt). ZWEI Anlässe, beide richtig: (1) die beantworteten Rückfragen aus der Lastgang-Analyse; (2) alles, was der User von sich aus über seinen Verbrauch erzählt — typischerweise direkt nach analyze_load_profile, wenn er die Auffälligkeit auf dem Schirm hat und sie ungefragt erklärt ('der Verbrauch in der Früh ist der Boiler', 'das Auto lade ich unregelmäßig'). Speichere das, statt es im Gesprächsverlauf zu lassen: aus Viertelstundenwerten ist es nie wieder ableitbar. Liegt dagegen eine KONKRETE offene Rückfrage zu einem Ereignis vor, gehört sie über answer_lastgang_question beantwortet — nur so wird sie als erledigt vermerkt. Validiert deterministisch gegen die bekannten Felder — unbekannte Felder oder unplausible Werte werden mit error_class=validation_rejected abgelehnt (nichts wird gespeichert). Gleiche Ablehnungs-Regeln wie submit_invoice_facts.
Eingabe
fakten*arrayListe der Fakten als Feld/Wert/Beleg-Objekte — beantwortete Rückfragen ebenso wie ungefragt Erzähltes. Ausgabe (oberste Ebene)
anzahl_faktenintegerWie viele Fakten übernommen wurden. gespeichertbooleanimmer true — bei Ablehnung wird NICHTS gespeichert. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
tariff_compareRechenwerk (energietools) nur lesendVergleicht den aktuellen STROMtarif mit tagesaktuellen Alternativen (Rechenweg je Alternative). Braucht KEINEN Lastgang und KEINE serie_ref — die fünf Rechnungswerte unten reichen (serie_ref gehört zu spot_backtest/load_trend, nicht hierher). Bei Tarif-/Preisfragen dieses Tool nutzen statt Websuche: der Katalog ist tagesaktuell und jede Zahl kommt mit Rechenweg. Aktuell nur Strom: energieart ist PFLICHT (strom|gas) und 'gas' wird sauber als not_supported abgelehnt — setze es bewusst, nicht raten. Erfrage beim User zuerst: PLZ, Jahresverbrauch (kWh), aktueller Lieferant, Arbeitspreis (ct/kWh brutto), Grundgebühr (€/Monat brutto). Beträge in €/kWh bzw. €/Jahr vorher in ct/kWh bzw. €/Monat umrechnen. Je Tarif im Ergebnis: rabatt_befristet=true bedeutet ein NUR im 1. Jahr wirkender Neukundenbonus — dann jahreskosten_jahr2_eur (Preis ab Jahr 2) mit ausweisen, den Bonus NICHT als Dauer-Ersparnis verkaufen. Beachte netzkosten_vollstaendig: ist es false, ist kein Netzbetreiber hinterlegt und der €-Betrag heißt energiepreis_anteil_eur (nur Energiepreis-Anteil, NICHT jahreskosten_eur) — dem User NICHT als Gesamtrechnung präsentieren. Die VNB-Auflösung macht der Gateway (nb_key vorgelöst): gib den zaehlpunkt mit, wenn du ihn hast. netzbetreiber_herkunft im Ergebnis sagt, worauf sie beruht — bei 'plz' ist der Netzbetreiber ANGENOMMEN (und die Netzkosten mit ihm), bei 'zaehlpunkt' belegt. Vor einer Gesamtkosten-Aussage auf 'plz'-Basis den Zählpunkt erfragen, statt die Zahl als exakt zu verkaufen. Bei netzbetreiber_herkunft='plz_mehrdeutig' (PLZ mit zwei Netzbetreibern, kein Zählpunkt) die Spanne (netzkosten_spanne_eur_jahr, je Tarif gesamtkosten_spanne_eur) mit beiden Netzbetreibern nennen und nach dem Zählpunkt von der Rechnung fragen.
Eingabe
aktuelle_grundgebuehr_brutto_eur_monat*numberGrundgebühr brutto in €/Monat (nicht €/Jahr) aktueller_energiepreis_brutto_ct_kwh*numberArbeitspreis brutto in ct/kWh (nicht €/kWh) aktueller_lieferant*stringName des aktuellen Stromlieferanten. energieart*stringPflicht: 'strom' (aktuell einziger Support) oder 'gas' (→ not_supported) jahresverbrauch_kwh*numberJahresverbrauch in kWh. plz*stringPLZ des Lieferorts (4-stellig). zaehlpunktstringOptional, aber für die Netzkosten der bessere Schlüssel: 33-stelliger AT-Zählpunkt aus den Invoice-Fakten oder list_load_series (kein Abtippen durch den User). Er benennt den Netzbetreiber eindeutig; ohne ihn wird er aus der PLZ angenommen und liegt in Gemeinden mit fremdem Netzbetreiber falsch. Ausgabe (oberste Ebene)
dataobjectVergleichsergebnis der energietools-Capability 'tariff_compare'. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. metaobject | nullHerkunft der Rechnung/Daten aus energietools (quelle, stand, snapshot_version) — Inhalt variiert je Capability. netzbetreiber_herkunftstring | nullWorauf die Netzbetreiber-Auflösung beruht: 'zaehlpunkt' = belegt, 'plz' = ANGENOMMEN (und mit ihm die Netzkosten), 'plz_mehrdeutig' = die PLZ gehört zu mehreren Netzbetreibern, kein Zählpunkt: Netzkosten als Spanne (netzkosten_spanne_eur_jahr), nach dem Zählpunkt fragen. Gateway-seitig gesetzt, nicht von energietools. netzbetreiber_kandidatenarrayNUR bei 'plz_mehrdeutig': die infrage kommenden Netzbetreiber mit ihren Netzkosten für den angegebenen Verbrauch. netzkosten_spanne_eur_jahrobjectNUR bei 'plz_mehrdeutig': niedrigste und höchste Netzkosten im Jahr. netzkosten_spanne_rechenweganyNUR bei 'plz_mehrdeutig': Rechenweg der Netzkosten-Spanne (No-LLM-Math). okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
upsert_pageGedächtnis (engram)Speichert eine Seite genau so, wie du sie übergibst (1:1, keine KI-Bearbeitung). Der Server prüft hier nur, dass die Seite nicht verwaist (nirgends verlinkt) ist; die Quellen-Anker und die Typisierung werden erst bei compile_done geprüft. Überschreibschutz: eine bestehende Seite wird nur mit overwrite=true ersetzt — lies sie vorher mit get_page. Bei einer bestehenden Seite werden Text UND Titel ersetzt: den aktuellen Titel mitgeben, sonst wird er überschrieben.
Eingabe
body*stringVoller Seitentext (Markdown), wird 1:1 gespeichert. key*stringSeiten-Key (Slug); bei bestehenden Seiten den Key aus search_pages nehmen. overwritebooleantrue = bestehende Seite ersetzen; Default false = Ablehnung, wenn sie schon existiert. title*stringSeitentitel. Wird bei jedem Schreiben gesetzt, auch bei einem Update (overwrite=true) — bei bestehenden Seiten den aktuellen Titel (aus get_page) mitgeben, sonst wird er überschrieben. Ausgabe (oberste Ebene)
hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). proxiedbooleanimmer true — die Nutzlast kommt aus der engram-Instanz, nicht aus dem Gateway. resultobject | nullRohes MCP-Tool-Result der Instanz: {content: [...], isError: bool}. Der eigentliche Inhalt steckt als JSON-Text in content[0].text. toolstringName des proxierten Vault-Tools. workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht.
Berater-Werkzeuge
Ein Beraterkonto sieht zusätzlich 6 Portfolio-Werkzeuge. Für Zugriff auf einen Kundenbereich gibst du deinem Berater die Freigabe im eigenen Konto, nicht umgekehrt.
-
invite_clientBerater-PortfolioLädt einen Kunden per E-Mail-Adresse ein, den eigenen Bereich für diesen Berater freizugeben. Funktioniert auch, wenn der Kunde noch kein Gridbert-Konto hat. Der Kunde entscheidet selbst: die Einladung verschafft KEINEN Zugriff, sie führt nur zu einer Anfrage, der er in seinem Konto zustimmen muss. Das Result enthält einen Einladungslink, den der Berater auch selbst weitergeben kann.
Eingabe
bezeichnung*stringAnzeigename dieses Mandats im Berater-Portfolio, z.B. 'Haushalt Familie K, Wien 1070' oder 'Betrieb Mustermann GmbH' einwilligungstringVermerk, wie die Zusage zustande kam, z.B. 'Am Telefon besprochen, 05.08.2026'. Der Kunde sieht ihn in seinem Konto — er ersetzt die Zustimmung NICHT. email*stringE-Mail-Adresse des Kunden kundentypstring'betrieb' für Unternehmen mit Lastprofilzähler; dann Sie-Form und Betriebs-Wortwahl in allen Mails. Weggelassen = 'haushalt' (Du-Form, Default). Ausgabe (oberste Ebene)
bezeichnungstringAnzeigename des Mandats im Portfolio. eingeladenstringDie eingeladene E-Mail-Adresse. einladungslinkstringLink, den der Berater auch selbst weitergeben kann. gueltig_bisstringAblauf der Einladung als ISO-Zeitstempel. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. zugriffstringImmer der Wartezustand — nie ein 'ja'. Der Kunde entscheidet. -
list_workspacesBerater-Portfolio nur lesendListet die Arbeitsbereiche des Beraters: eigener Bereich + alle Kunden mit aktiver Freigabe (Grant), inkl. Datenstand.
Ausgabe (oberste Ebene)
aktiver_workspacestring | nullSlug des aktiven Bereichs, null = eigener Bereich. eigener_bereichobjectDer eigene Bereich des Beraters. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. workspacesarrayEin Eintrag je aktivem Grant. -
portfolio_foerder_checkBerater-Portfolio nur lesendPrüft für JEDEN Mandanten, ob eine offene Förderung (PV, Speicher, Heizungstausch) zu seinem Profil passt, und gibt je Mandant eine Ampel zurück: 'passt', 'passt_vielleicht', 'passt_nicht', 'daten_fehlen' (fehlende_fakten nennt, was beim Mandanten nachzutragen ist), 'nicht_verfuegbar'. Dazu die Förder-IDs und das Antragsfenster. Beantwortet 'bei welchen Kunden gibt es Förder-Potenzial?'. Mit foerderung_id nur diese eine Förderung ('bei wem passt genau diese?'). Das Result enthält KEINE Förderbeträge — nenne dem Berater keine. Für die Förderhöhe eines Mandanten mit select_workspace dorthin wechseln und foerderung_und_beratung rufen.
Eingabe
foerderung_idstringOptional: ID einer offenen Förderung (z.B. aus foerderung_und_beratung). Weggelassen = alle offenen Förderungen der Kategorien pv, speicher, heizungstausch. Ausgabe (oberste Ebene)
ampel_bedeutungobject | nullLegende der fünf Ampelwerte. anzahlintegerAnzahl Mandanten. datenstandstring | nullRecherche-Stand der Förderdaten. foerderung_idstringNur wenn eine einzelne Förderung geprüft wurde. foerderungenobjectLegende der genannten Förderungen je ID (Name, Stelle, Link) — ohne Förderhöhe. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. mandantenarrayEine Ampel-Zeile je Mandant. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
portfolio_overviewBerater-Portfolio nur lesendStatus-Übersicht über ALLE Mandanten des Beraters auf einen Blick: je Mandant, ob eine Rechnung und ob Lastgang-Daten hinterlegt sind, wann zuletzt gerechnet wurde, und ob der Zugriff gerade trägt. Beantwortet 'bei wem fehlt was?'. Enthält BEWUSST keine Zahlen — keinen Verbrauch, keinen Preis, keinen Betrag. Für die Werte eines Mandanten mit select_workspace dorthin wechseln.
Ausgabe (oberste Ebene)
anzahlintegerAnzahl Mandanten. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. mandantenarrayEine Status-Zeile je Mandant. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
portfolio_tariff_checkBerater-Portfolio nur lesendRechnet für JEDEN Mandanten mit hinterlegter Rechnung einen Tarifvergleich und gibt je Mandant eine Ampel zurück: 'ok' (unter 5 % Ersparnis), 'pruefen' (5-15 %), 'handeln' (über 15 %), 'keine_daten' (keine vollständige Rechnung), 'nicht_verfuegbar'. Beantwortet 'bei wem lohnt ein genauerer Blick?'. Das Result enthält KEINE Beträge — nenne dem Berater keine, sie stehen nicht drin. Für den vollen Rechenweg eines Mandanten mit select_workspace dorthin wechseln und tariff_compare rufen. Beachte netzkosten_vollstaendig je Zeile: bei false beruht die Ampel nur auf dem Energiepreis-Anteil.
Ausgabe (oberste Ebene)
ampel_bedeutungobject | nullLegende der fünf Ampelwerte. anzahlintegerAnzahl Mandanten. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. mandantenarrayEine Ampel-Zeile je Mandant. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht. -
select_workspaceBerater-PortfolioWechselt den aktiven Arbeitsbereich des Beraters (Kunde per Slug oder eindeutigem Label; 'eigen' = eigener Bereich). Die Auswahl gilt account-weit bis zum nächsten Wechsel.
Eingabe
workspace*stringKunden-Slug oder eindeutiges Label aus list_workspaces; 'eigen' wechselt zurück in den eigenen Bereich Ausgabe (oberste Ebene)
aktiver_workspacestring | nullSlug des NEU gewählten Bereichs. null = eigener Bereich. hinweisstring | nullOptionaler Hinweis ans Modell (F14) — Datenstand-Warnung, nächster Schritt, Granularitäts-Caveat. Darf null sein. labelstringAnzeigename des neuen Bereichs. okbooleanimmer true. Fehler kommen als isError ohne structuredContent zurück (errors.error_result: {ok:false, error_class, fehler}). workspaceobjectNur bei Berater-Accounts: der Arbeitsbereich, in dem dieser Call gelandet ist (dispatch.py stempelt ihn zentral an JEDES Result). Haushalts-Results tragen das Feld nicht.
Ein Beraterkonto sieht zusätzlich sechs Portfolio-Werkzeuge (oben gelistet) und bekommt dieselben Werkzeug-Texte in der Sie-Form, wenn ein Kunde als 'betrieb' markiert ist (kundentyp='betrieb'). Der Katalog selbst ändert sich dadurch nicht.
HTTP-Endpunkte
46 Endpunkte, nach Aufgabe gruppiert. Die Auth-Spalte zeigt, was ein Aufruf braucht: ein Sitzungs-Cookie (Browser, angemeldet), ein Bearer-Token (MCP-Client), einen signierten Link aus einer E-Mail, oder gar nichts.
Konto
-
POST/accounts/deleteSession-CookieLöscht das eigene Konto endgültig, inklusive Instanz, Zugängen und Messwerten.
Request
bestaetigung*stringDie eigene E-Mail-Adresse, zur Bestätigung wörtlich eingetippt. Antwort
JSON
fertig: true, wenn die Löschung vollständig durch ist.konto_geloescht: true/false.offen: Schritte, die beim nächsten Aufruf weiterlaufen, falls einer offen blieb.
Fehler
400bestaetigung_stimmt_nicht: Die eingetippte Adresse stimmt nicht mit der Konto-Adresse überein.401no_session: Keine Sitzung.429rate_limited: Zu viele Versuche von dieser IP-Adresse.503loeschung_unvollstaendig: Ein Teilschritt war gerade nicht erreichbar. Derselbe Aufruf macht dort weiter.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
POST/accounts/lastgangSession-CookieLädt eine Verbrauchsdatei aus dem Netzbetreiber-Portal hoch, die zweite Quelle für Lastgang-Daten neben der Datenfreigabe.
Request
datei*multipart/form-dataDie Portal-Exportdatei. Antwort
JSON, Status 201
status: 'gespeichert'.quellformat: Erkanntes Dateiformat.serien: Eine Zeile je erkannter Zählserie, mit Zeitraum und Anzahl neuer/aktualisierter Messwerte.
Fehler
401no_session: Keine Sitzung.413file_too_large: Datei über dem Limit.422unreadable: Datei nicht lesbar oder falsches Format.429rate_limited: Zu viele Uploads von dieser IP-Adresse.503storage_unavailable: Lastgang-Speicher gerade nicht angebunden.
Limits
- 3 Uploads pro Minute je IP-Adresse
- Datei bis 20 MB
-
POST/accounts/loginöffentlichMeldet ein bestätigtes Konto an und setzt das Sitzungs-Cookie für den Kontobereich.
Request
email*stringE-Mail-Adresse. password*stringPasswort. Antwort
JSON + Set-Cookie
ok: true bei Erfolg.
Fehler
401invalid_credentials: E-Mail-Adresse oder Passwort falsch, bewusst eine Meldung für beide Fälle.429rate_limited: Zu viele Anmeldeversuche von dieser IP-Adresse.
Limits
- 10 Anmeldeversuche pro Minute je IP-Adresse
-
POST/accounts/logoutSession-CookieBeendet die Sitzung und löscht das Sitzungs-Cookie.
Antwort
Status 204, kein Inhalt
-
GET/accounts/meSession-CookieZeigt den Stand des eigenen Kontos: Bestätigung, Einrichtung, Kundentyp, Alarm-Einstellung.
Antwort
JSON
email: Hinterlegte Adresse.email_verified: Bestätigt oder nicht.provisioning_status: Stand der Einrichtung.instance_slug: Kennung der eigenen Instanz.created_at: Angelegt am.alarm_mails: true = bekommt Nachrichten, wenn dem Zähler etwas auffällt.kundentyp: haushalt oder betrieb, steuert Du/Sie in den Texten.
Fehler
401no_session: Keine oder abgelaufene Sitzung.
-
POST/accounts/password/reset-confirmöffentlichSetzt mit dem Link-Token aus der Reset-Mail ein neues Passwort.
Request
token*stringReset-Token aus der Mail. password*stringNeues Passwort. Antwort
JSON
ok: true bei Erfolg.
Fehler
401kein fester Code: 'error' trägt Klartext, keinen festen Code: z. B. 'invalid or already used reset token', 'reset token expired' oder 'unknown account'.429rate_limited: Zu viele Versuche von dieser IP-Adresse.
Limits
- 5 Anfragen pro Minute je IP-Adresse
- Link 1 Stunde gültig
-
POST/accounts/password/reset-requestöffentlichStößt den Passwort-Reset an und verschickt bei einer bekannten Adresse einen Link.
Request
email*stringE-Mail-Adresse. Antwort
JSON, Status 202
status: 'accepted': immer dieselbe Antwort, unabhängig davon, ob die Adresse existiert.
Fehler
429rate_limited: Zu viele Anfragen von dieser IP-Adresse.
Limits
- 5 Anfragen pro Minute je IP-Adresse
-
POST/accounts/rechnungBearer-TokenNimmt eine Rechnungsdatei entgegen und gibt eine Kennung zurück, mit der read_invoice_facts sie liest (oder get_invoice_reading_task, wenn ein eigenes Modell lesen soll). Die Datei passt nicht durch die Werkzeug-Schnittstelle und geht deshalb diesen Weg.
Request
datei*multipart/form-dataDie Rechnung als PDF, JPG oder PNG. Antwort
JSON, Status 201
rechnung_ref: Kennung für read_invoice_facts, get_invoice_reading_task und check_invoice_reading. Eine Stunde gültig.seiten: Seitenzahl der Datei.quellformat: pdf, jpeg oder png, aus der Datei erkannt und nicht aus dem Namen.textebene: true heisst, die Rechnung wird ohne Modell exakt gelesen. false heisst Foto oder Scan; dafür ist der Modell-Pfad noch nicht gebaut.
Fehler
401no_session: Keine Sitzung und kein gültiges Token.413file_too_large: Datei über dem Limit.415unsupported_format: Kein PDF, JPG oder PNG.422unreadable: Datei nicht zu öffnen oder zu viele Seiten.429rate_limited: Zu viele Uploads von dieser IP-Adresse.
Limits
- 3 Rechnungs-Uploads pro Minute je IP-Adresse
- höchstens 20 MB pro Rechnungsdatei
-
DELETE/accounts/rechnung/{rechnung_ref}Bearer-TokenLöscht eine hochgeladene Rechnung sofort, sobald die Werte bestätigt sind. Ohne diesen Aufruf verschwindet sie spätestens nach einer Stunde von selbst.
Request
rechnung_ref*Pfad-SegmentDie Kennung aus dem Upload. Antwort
Status 204, kein Inhalt
Fehler
401no_session: Keine Sitzung und kein gültiges Token.
-
POST/accounts/registeröffentlichLegt ein neues Konto an und verschickt eine Bestätigungsmail.
Request
email*stringE-Mail-Adresse, gleichzeitig der Login-Name. password*stringPasswort, mindestens 8 Zeichen. Antwort
JSON, Status 201
account_id: Kennung des neuen Kontos.
Fehler
422kein fester Code: 'error' trägt Klartext, keinen festen Code: z. B. 'gültige E-Mail erforderlich' oder 'E-Mail bereits registriert', je nachdem was nicht passt.429rate_limited: Zu viele Registrierungen von dieser IP-Adresse.
Limits
- 5 Registrierungen pro Minute je IP-Adresse
- 20 neue Konten pro Tag je IP-Adresse
-
POST/accounts/resend-verifyöffentlichSchickt die Bestätigungsmail erneut, wenn sie nicht angekommen ist.
Request
email*stringE-Mail-Adresse. Antwort
JSON, Status 202
status: 'accepted': immer dieselbe Antwort, unabhängig davon, ob die Adresse existiert.
Fehler
429rate_limited: Zu viele Anfragen von dieser IP-Adresse.
Limits
- 5 Anfragen pro Minute je IP-Adresse
-
POST/accounts/verifyöffentlichBestätigt die E-Mail-Adresse mit dem Token aus der Bestätigungsmail und stößt danach die Einrichtung des Kontos im Hintergrund an.
Request
account_id*stringKontokennung aus der Registrierung. token*stringBestätigungs-Token aus der Mail. Antwort
JSON
verified: true, wenn bestätigt.provisioning_scheduled: true: die Einrichtung läuft im Hintergrund weiter.
Fehler
401kein fester Code: 'error' trägt Klartext, keinen festen Code: z. B. 'unknown account or no pending verification' oder 'invalid verification token'.429rate_limited: Zu viele Bestätigungsversuche von dieser IP-Adresse.
Limits
- 10 Bestätigungen pro Minute je IP-Adresse
Zugriff & Freigaben
-
GET/accounts/einladungöffentlichZeigt, wer einen Zugriff angeboten hat, bevor man sich dafür anmeldet oder registriert.
Request
token*queryEinladungs-Token aus dem Einladungslink. Antwort
JSON
berater: E-Mail-Adresse der einladenden Person.email: Eingeladene Adresse.bezeichnung: Name des Mandats.gueltig_bis: Ablauf der Einladung.kundentyp: haushalt oder betrieb.
Fehler
404kein fester Code: 'error' trägt Klartext, keinen festen Code: 'kein Token', wenn der Query-Parameter fehlt, oder 'Einladung nicht gefunden', wenn der Token unbekannt, zurückgezogen oder abgelaufen ist. Bewusst dieselbe Antwort in allen Fällen, damit der Statuscode keine Auskunft über ein fremdes Konto verrät.
-
POST/accounts/me/alarmeSession-CookieSchaltet die Nachrichten ein oder aus, die kommen, wenn dem eigenen Zähler etwas auffällt.
Request
aus*booleantrue = Nachrichten abstellen, false = wieder einschalten. Antwort
JSON
alarm_mails: Neuer Stand: true = eingeschaltet.
Fehler
401no_session: Keine Sitzung.422kein fester Code: 'error' trägt Klartext, keinen festen Code: 'invalid body' bei kaputtem JSON, 'aus muss true oder false sein' wenn das Feld fehlt oder kein Wahrheitswert ist.429rate_limited: Zu viele Aktionen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
GET/accounts/me/freigabenSession-CookieZeigt, wer auf die eigenen Daten schauen darf, inklusive beendeter Freigaben.
Antwort
JSON
freigaben: Eine Zeile je Freigabe: Status, Gegenseite, Zeitpunkte.
Fehler
401no_session: Keine Sitzung.
-
POST/accounts/me/freigaben/{grant_id}/widerrufenSession-CookieBeendet eine Freigabe, wirkt ab dem nächsten Aufruf der Gegenseite.
Request
grant_id*PfadKennung der Freigabe. Antwort
JSON
status: Neuer Zustand der Freigabe.
Fehler
401no_session: Keine Sitzung.404kein fester Code: 'error' trägt Klartext ('Freigabe nicht gefunden.'), keinen festen Code: Freigabe existiert nicht oder gehört nicht zu diesem Konto.429rate_limited: Zu viele Aktionen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
POST/accounts/me/freigaben/{grant_id}/zustimmenSession-CookieStimmt einer angebotenen Freigabe zu. Erst danach kann die Gegenseite die Daten sehen.
Request
grant_id*PfadKennung der Freigabe. Antwort
JSON
status: Neuer Zustand der Freigabe.
Fehler
401no_session: Keine Sitzung.404kein fester Code: 'error' trägt Klartext ('Freigabe nicht gefunden.'), keinen festen Code: Freigabe existiert nicht oder gehört nicht zu diesem Konto.429rate_limited: Zu viele Aktionen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
GET/accounts/me/verbindungenSession-CookieZeigt, welche Chatbots gerade mit dem eigenen Konto verbunden sind.
Antwort
JSON
verbindungen: Eine Zeile je verbundener App: Name, ob Dauerzugriff, gültig bis, zuletzt verbunden.
Fehler
401no_session: Keine Sitzung.
-
POST/accounts/me/verbindungen/entziehenSession-CookieTrennt eine verbundene App sofort, nicht erst zum Ablauf des Zugangs.
Request
client_id*stringKennung der zu trennenden App, aus der Verbindungsliste. Antwort
JSON
entzogen: Anzahl entwerteter Zugänge.verbindungen: Die aktualisierte Liste.
Fehler
401no_session: Keine Sitzung.422kein fester Code: 'error' trägt Klartext, keinen festen Code: 'invalid body' bei kaputtem JSON, 'client_id fehlt' wenn das Feld fehlt oder leer ist.429rate_limited: Zu viele Aktionen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
GET/alarm/abmeldensignierter Link aus einer E-MailEin-Klick-Abmeldung von den Alarm-Mails direkt aus der E-Mail, ohne Anmeldung.
Request
token*querySignierter Abmelde-Token aus der Mail. Antwort
HTML-Seite
Fehler
400kein fester Code: Token nicht lesbar. Die Seite zeigt einen Hinweis, keine Auskunft über die Adresse.
App-Session
-
GET/app/aktivitaetSession-CookieMitschrift, was der Chat zuletzt an Werkzeugen aufgerufen hat: Werkzeug, Zeit, Erfolg, nie Argumente oder Ergebnisse.
Request
tagequery, 1–30Zeitraum zurück, Default 1 Tag. Antwort
JSON
eintraege: Eine Zeile je Aufruf: Werkzeug, Familie, Zeitpunkt, Erfolg, Fehlerklasse.
Fehler
401no_session: Keine Sitzung.422invalid_tage: 'tage' außerhalb 1–30.
-
POST/app/eda-abfrageSession-CookieStößt eine Datenfreigabe-Anfrage beim Netzbetreiber aus der App heraus an, dasselbe Werkzeug wie im Chat.
Request
(Body)*objectDie Argumente des Werkzeugs request_data_release (zaehlpunkt, plz, energy_direction). Antwort
JSON: dasselbe Result wie im Chat
Fehler
401no_session: Keine Sitzung.415unsupported_media_type: Content-Type ist nicht application/json.422invalid_body: Body kein gültiges JSON-Objekt.429rate_limited: Zu viele Aufrufe von dieser IP-Adresse.
Limits
- 60 Aufrufe pro Minute je IP-Adresse
-
POST/app/rueckfrageSession-CookieBeantwortet eine offene Lastgang-Rückfrage aus der App heraus, dasselbe Werkzeug wie im Chat.
Request
(Body)*objectDie Argumente des Werkzeugs answer_lastgang_question. Antwort
JSON: dasselbe Result wie im Chat
Fehler
401no_session: Keine Sitzung.415unsupported_media_type: Content-Type ist nicht application/json.422invalid_body: Body kein gültiges JSON-Objekt.429rate_limited: Zu viele Aufrufe von dieser IP-Adresse.
Limits
- 60 Aufrufe pro Minute je IP-Adresse
-
POST/app/toolSession-CookieRuft ein lesendes Werkzeug des Katalogs über das Sitzungs-Cookie auf, dieselbe Antwort wie im Chat.
Request
name*stringWerkzeugname aus dem Katalog. argumentsobjectEingabe des Werkzeugs, wie im inputSchema beschrieben. Antwort
JSON: dasselbe Result wie ein MCP-tools/call
Fehler
401no_session: Keine Sitzung.403nur_lesend: Das Werkzeug ist über diesen Weg nicht aufrufbar. Nur lesende Werkzeuge, unbekannte Namen bekommen dieselbe Antwort.415unsupported_media_type: Content-Type ist nicht application/json.422invalid_body: Body kein gültiges JSON-Objekt oder 'name' fehlt.429rate_limited: Zu viele Aufrufe von dieser IP-Adresse.
Limits
- 60 Aufrufe pro Minute je IP-Adresse
Lastgang-Rückfragen
-
GET/lastgang/antwortsignierter Link aus einer E-MailZeigt die Antwortmöglichkeiten zu einer Auffälligkeit aus der Alarm-Mail.
Request
t*queryVerschlüsselter Token aus der Mail (Ereignis + Option). Antwort
HTML-Seite
Fehler
404kein fester Code: Link gilt nicht mehr: schon beantwortet oder abgeschnitten.
-
POST/lastgang/antwortsignierter Link aus einer E-MailSpeichert die gewählte oder frei getippte Antwort zu einer Auffälligkeit.
Request
t*string (Form)Derselbe Token wie beim Anzeigen. freitextstring (Form), bis 200 ZeichenEigene Erklärung, wenn keine der Optionen passt. Antwort
HTML-Seite (Dankesseite, ggf. mit Rückschau)
Fehler
404kein fester Code: Link gilt nicht mehr.429kein fester Code: Zu viele Versuche von dieser IP-Adresse in kurzer Zeit.500kein fester Code: Speichern fehlgeschlagen, später erneut versuchen.
Limits
- 10 Antworten pro Minute je IP-Adresse
Match meinen Strom
-
POST/matchme/demoöffentlichRechnet ohne Anmeldung ein Beispielergebnis der Partnersuche vor, aus frei eingegebenen Eckdaten.
Request
jahresverbrauch_kwh*numberJahresverbrauch laut Rechnung. waermepumpe*booleanWärmepumpe vorhanden? eauto*booleanE-Auto vorhanden? warmwasser_elektrisch*booleanWarmwasser elektrisch? Antwort
JSON
Fehler
422validation_rejected: Eingabe außerhalb der erlaubten Werte.429rate_limited: Zu viele Aufrufe: je IP-Adresse und insgesamt.
Limits
- 5 Aufrufe pro Minute je IP-Adresse
- 60 Aufrufe pro Minute insgesamt (zusätzliche Bremse, nicht an eine einzelne IP-Adresse gebunden)
-
POST/matchme/einladungSession-CookieErzeugt einen persönlichen Einladungslink fürs Matching mit einer bestimmten Person.
Antwort
JSON, Status 201
link: Einladungslink zum Weitergeben.gueltig_bis: Ablaufdatum.
Fehler
401no_session: Keine Sitzung.429rate_limited: Zu viele Aktionen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
POST/matchme/einladung/einloesenSession-CookieNimmt eine Matching-Einladung an und bildet das Paar.
Request
token*stringToken aus dem Einladungslink. Antwort
JSON
partner_label: E-Mail-Adresse der einladenden Person.
Fehler
401no_session: Keine Sitzung.429rate_limited: Zu viele Aktionen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
POST/matchme/einladung/vorschauöffentlichZeigt vor der Anmeldung, von wem eine Matching-Einladung kommt.
Request
token*stringToken aus dem Einladungslink, nur im Body, nie in der Adresszeile. Antwort
JSON
von: E-Mail-Adresse der einladenden Person.
Fehler
429rate_limited: Zu viele Anfragen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
POST/matchme/einladung/widerrufSession-CookieZieht eine eigene, noch offene Einladung zurück.
Request
id*stringKennung der Einladung. Antwort
JSON
Fehler
401no_session: Keine Sitzung.422validation_rejected: 'id' fehlt.429rate_limited: Zu viele Aktionen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
GET/matchme/einladungenSession-CookieListet die eigenen Einladungen, verschickte und erhaltene.
Antwort
JSON
einladungen: Eine Zeile je Einladung: Status, Rolle, Zeitpunkte, Partner (nur bei eingelöst).
Fehler
401no_session: Keine Sitzung.
-
GET/matchme/matchesSession-CookieZeigt die Rangliste passender Partnerprofile, ohne Namen, ohne Zählpunkte, ohne genaue Jahresmengen.
Antwort
JSON
Fehler
401no_session: Keine Sitzung.403kein_optin: Eigenes Profil ist nicht für die Partnersuche freigegeben.409kein_lastgang: Keine verwertbare eigene Verbrauchsserie.
-
GET/matchme/optinSession-CookieZeigt, ob das eigene Profil gerade für die Partnersuche anderer mitgerechnet werden darf.
Antwort
JSON
status: 'active' | 'revoked' | 'none'.
Fehler
401no_session: Keine Sitzung.
-
POST/matchme/optinSession-CookieGibt das eigene Profil für die Partnersuche frei.
Antwort
JSON
status: 'active'.
Fehler
401no_session: Keine Sitzung.429rate_limited: Zu viele Aktionen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
POST/matchme/optin/widerrufSession-CookieNimmt das eigene Profil wieder aus der Partnersuche.
Antwort
JSON
status: 'revoked' oder 'none', wenn es ohnehin keinen aktiven Opt-in gab.
Fehler
401no_session: Keine Sitzung.429rate_limited: Zu viele Aktionen von dieser IP-Adresse.
Limits
- 20 Aktionen pro Minute je IP-Adresse
-
GET/matchme/statusSession-CookieZeigt den eigenen Stand: Opt-in, Zählpunkt (maskiert), Datenlage.
Antwort
JSON
Fehler
401no_session: Keine Sitzung.
Kontakt
-
POST/contactöffentlichKontaktformular der Website, schickt eine Nachricht.
Request
(Formularfelder)*objectName, E-Mail-Adresse, Nachricht. Antwort
JSON, Status 202
status: 'accepted'.
Fehler
422kein fester Code: Body fehlt oder ungültig.429rate_limited: Zu viele Nachrichten von dieser IP-Adresse.
Limits
- 5 Nachrichten pro Minute je IP-Adresse
OAuth / Verbindung
-
GET/.well-known/oauth-authorization-serveröffentlichDiscovery-Dokument des Authorization Servers (RFC 8414): Endpunkte, Scopes, unterstützte Verfahren.
Antwort
JSON
-
GET/.well-known/oauth-protected-resourceöffentlichDiscovery-Dokument der geschützten Ressource (RFC 9728). Bindet mcp.gridbert.at an seinen Authorization Server.
Antwort
JSON
-
GET/authorizeöffentlich (Anmelde-/Verbindungsseite)Zeigt die Anmelde- bzw. Registrierungsseite, über die ein Mensch die Verbindung seines Chatbots bestätigt.
Request
client_id, redirect_uri, …*queryOAuth-Parameter des Connectors (PKCE, Scope, State). Antwort
HTML-Seite
Fehler
400kein fester Code: Pflichtangaben fehlen oder die Anfrage ist unbekannt.
-
POST/authorizeöffentlich (Zugangsdaten im Formular, kein Cookie/Header)Verarbeitet Anmeldung, Registrierung oder Bestätigung auf der Verbindungsseite und leitet mit einem Autorisierungs-Code zurück.
Request
intentstring'login' (Default), 'register' oder 'connect': welches der drei Formulare abgeschickt wurde. email, passwordstringBei Anmeldung oder Registrierung. client_id, redirect_uri, code_challenge, …*stringOAuth-Parameter, unverändert aus dem vorherigen Schritt übernommen. Antwort
302-Redirect mit Code (oder erneut die HTML-Seite bei einem Fehler)
Fehler
401access_denied: Anmeldedaten falsch.429rate_limited: Zu viele Anmeldeversuche von dieser IP-Adresse.
Limits
- 30 Anmeldungen pro Minute je IP-Adresse
-
GET/jwks.jsonöffentlichÖffentlicher Schlüssel, mit dem Zugangs-Token signiert sind (JWK Set).
Antwort
JSON
-
POST/mcpBearer-TokenDer eine JSON-RPC-Endpunkt, über den ein verbundener Chatbot alle Werkzeuge aufruft.
Request
jsonrpc, id, method, params*JSON-RPC 2.0method ist eine der unterstützten Methoden (initialize, tools/list, tools/call, notifications/initialized). Antwort
JSON-RPC-Antwort mit content[] und structuredContent
Fehler
401invalid_token: Kein oder ungültiges Bearer-Token.400parse error: Body kein gültiges JSON.400unknown method: Nicht unterstützte Methode.
Limits
- 30 Aufrufe Startguthaben
- füllt mit 0.5 Aufrufen pro Sekunde nach
- 2000 Aufrufe pro Tag je Konto
- Anfrage bis 256 KB
- Werkzeug-Eingabe bis 150 KB
-
POST/registeröffentlich (Client-Registrierung, DCR)Dynamic Client Registration (RFC 7591). Ein Chatbot meldet sich einmalig als Client an, bevor er eine Verbindung anbietet.
Request
client_namestringAnzeigename des Clients. redirect_uris*string[]Erlaubte Rücksprung-Adressen nach der Anmeldung. Antwort
JSON, Status 201
client_id: Neue Client-Kennung.token_endpoint_auth_method: 'none': öffentlicher Client mit PKCE.
Fehler
400invalid_client_metadata: Body kein gültiges JSON.422invalid_redirect_uri: Rücksprung-Adresse nicht erlaubt oder Cap erreicht.
Limits
- 5 Registrierungen pro Minute je IP-Adresse
- höchstens 20 offene Client-Registrierungen je IP-Adresse
-
POST/tokenöffentlich (Client-Zugangsdaten im Body)Tauscht einen Autorisierungs-Code oder ein Refresh-Token gegen ein Zugangs-Token.
Request
grant_type*string'authorization_code' oder 'refresh_token'. code, redirect_uri, client_id, code_verifierstringBei grant_type=authorization_code (PKCE). refresh_token, client_idstringBei grant_type=refresh_token. Antwort
JSON
access_token: Kurzlebiges Zugangs-Token.refresh_token: Langlebiges Token zum Erneuern.expires_in: Gültigkeit des Zugangs-Tokens in Sekunden.
Fehler
400unsupported_grant_type: grant_type ist weder authorization_code noch refresh_token.400invalid_grant: Code, Token oder PKCE passt nicht oder ist abgelaufen.429rate_limited: Zu viele Anfragen von diesem Client.
Limits
- 30 Token-Anfragen pro Minute je Client
Grenzen und Regeln
Rate-Limits
- 10 Anmeldeversuche pro Minute je IP-Adresse
- 10 Antworten pro Minute je IP-Adresse
- 10 Bestätigungen pro Minute je IP-Adresse
- 20 Aktionen pro Minute je IP-Adresse
- 20 neue Konten pro Tag je IP-Adresse
- 2000 Aufrufe pro Tag je Konto
- 3 Rechnungs-Uploads pro Minute je IP-Adresse
- 3 Uploads pro Minute je IP-Adresse
- 30 Anmeldungen pro Minute je IP-Adresse
- 30 Aufrufe Startguthaben
- 30 Token-Anfragen pro Minute je Client
- 5 Anfragen pro Minute je IP-Adresse
- 5 Aufrufe pro Minute je IP-Adresse
- 5 Nachrichten pro Minute je IP-Adresse
- 5 Registrierungen pro Minute je IP-Adresse
- 60 Aufrufe pro Minute insgesamt (zusätzliche Bremse, nicht an eine einzelne IP-Adresse gebunden)
- 60 Aufrufe pro Minute je IP-Adresse
- füllt mit 0.5 Aufrufen pro Sekunde nach
- höchstens 20 MB pro Rechnungsdatei
Größe der Anfragen
- Anfrage bis 16 KB auf den OAuth-/Konto-Endpunkten
- Anfrage bis 256 KB auf /mcp
- Werkzeug-Eingabe bis 150 KB je Werkzeug-Aufruf
- Datei bis 20 MB beim Lastgang-Upload
Herkunft im Browser
Nur eine hinterlegte Liste von Ursprüngen darf die Konto-Endpunkte per Browser-JavaScript ansprechen. Ein weiterer Ursprung wird über den Kontakt freigeschaltet, nicht selbst eingetragen.
Umgang mit Daten
- Rechnungs- und Lastgang-Inhalte stehen nie in Server-Logs, nur in den Tool-Ergebnissen selbst.
- Ein Fehler nennt die Fehlerklasse (z. B. rate_limited, not_configured), nie den internen Grund dahinter.
- Jede Euro-Zahl aus einem Werkzeug trägt ihren Rechenweg mit. Es wird nichts im Sprachmodell nachgerechnet.
Nicht Teil der Schnittstelle
GET /lastgang/antwort, POST /lastgang/antwort: Ein Link aus einer E-Mail, kein Aufruf für einen eigenen Client. Der Token gehört zu genau einer Mail.GET /alarm/abmelden: Ebenfalls ein Link aus einer E-Mail, kein Aufruf für einen eigenen Client./admin/*: Betreiber-Werkzeug, nicht öffentlich. Kein Bearer-Token dieser Schnittstelle öffnet es.
Stand
29. Oktober 2026, erzeugt aus dem Code, nicht von Hand nachgetragen. Ändert sich ein Werkzeug oder ein Endpunkt im Gateway, zieht diese Seite beim nächsten Build nach.
Du willst selbst etwas auf Gridbert bauen? Die Einrichtung je Agent zeigt den Klickweg, diese Seite die Referenz dahinter. Fragen dazu gehen an den Kontakt.