Docs · REST-API (Pro)

REST-API (Pro)

Lebende Referenz. Diese Seite wird bei jeder Schnittstellen-Änderung mitgepflegt. Brechende Änderungen erhalten eine neue Version; alte Versionen laufen mindestens 12 Monate weiter (siehe Versionierung). Die maschinenlesbare Spezifikation liegt als openapi-v1.yaml im Repo unter controlplane/public/docs/api/.

Die REST-API ist im Pro-Plan enthalten und erlaubt externen Programmen den Zugriff auf einen Mandanten: Zählerstände von Feldgeräten/Apps melden, Adressen geocodieren, das Leitungsnetz abfragen und Stammdaten per Massen-Import laden.

Basis-URL & Versionierung

Die API wird unter der Tenant-Subdomain ausgeliefert:

https://<slug>.quellstube.at/api/v1/…
  • Pfadbasierte Versionierung (/api/v1, künftig /api/v2, …).
  • Nicht-brechende Änderungen (neue Endpunkte, neue optionale Felder) gehen in die laufende Version.
  • Brechende Änderungen bekommen eine neue Version. Die alte läuft weiter und liefert solange die HTTP-Header Deprecation: true und Sunset: <Datum>.
  • Garantierter Mindest-Support: 12 Monate ab Ankündigung, bevor eine Version abgeschaltet wird.

Authentifizierung

Jede Anfrage trägt einen API-Schlüssel als Bearer-Token:

Authorization: Bearer qs_live_<slug>_<hex>

Schlüssel werden in der quellstube-App unter Verwaltung → API-Schlüssel erstellt (nur Tenant-Admins, nur Pro). Der Klartext wird einmalig bei der Erstellung angezeigt; gespeichert wird nur ein Hash.

  • Der im Schlüssel enthaltene Slug muss zur aufgerufenen Subdomain passen (sonst 403 wrong_tenant).
  • Nicht-Pro-Mandanten erhalten 402 pro_plan_required.
  • Pausierte (gekündigte/abgelaufene) Mandanten erhalten 402 tenant_suspended.

Berechtigungen (Scopes)

Ein Schlüssel trägt eine Teilmenge der App-Berechtigungen. Jeder Endpunkt verlangt einen passenden Scope:

Scope Deckt ab
zaehler Zähler & Ablesungen
network Leitungsnetz & Geocoding
stammdaten Kontakte/Objekte, Geocoding, Import
rechnungen_op Rechnungs-Kennzahlen (Statistik)

Fehlt der Scope: 403 insufficient_scope.

Rate-Limit

Standardmäßig 240 Anfragen/Minute je Mandant/IP. Überschreitung: 429.

Fehlerformat

Fehler sind JSON mit error-Code und optionaler message:

{ "error": "meter_not_found", "message": "Zaehler 12345 nicht gefunden." }
Status Beispiel-Codes
400 tenant_required, invalid_value, invalid_payload
401 missing_bearer_token, invalid_or_revoked_key
402 pro_plan_required, tenant_suspended
403 wrong_tenant, insufficient_scope
404 meter_not_found, no_active_plan
413 too_many_rows
429 (Rate-Limit)
503 geocoding_unavailable

Endpunkte (v1)

GET /api/v1 — Übersicht

Version, freigeschaltete Scopes, Endpunkt-Liste.

GET /api/v1/stats — Kennzahlen (Scope: zaehler | rechnungen_op | stammdaten)

Aggregiert, PII-frei: customers, properties, meters_active, open_invoices, active_period, missing_readings_active_period.

Standort & Leitungsnetz

GET /api/v1/geocode (Scope: network | stammdaten)

Adresse → WGS84-Koordinate (BEV-Adressregister, kein Personenbezug). Parameter: strasse (Pflicht), hausnummer, plz, ort.

GET /api/v1/geocode?strasse=Dorfstraße&hausnummer=12&plz=4030&ort=Linz
→ { "found": true, "lat": 48.2, "lng": 14.3 }

GET /api/v1/network/features (Scope: network)

Anlagen des aktiven (oder plan_id) Plans. Filter: type (z. B. hydrant), near=lat,lng + radius (Meter, nur Punkte, aufsteigend nach Entfernung), limit. PII-frei.

GET /api/v1/network/features?type=hydrant&near=48.2,14.3&radius=300
→ { "plan_id": 1, "count": 4, "features": [ { "id": 7, "feature_type": "hydrant", "lat": …, "distance_m": 41.3, … } ] }

GET /api/v1/network/features.geojson (Scope: network)

Wie oben, als GeoJSON-FeatureCollection.

Zähler & Ablesungen

GET /api/v1/meters (Scope: zaehler)

Zähler-Liste (Nummer, Standort, Typ, property_id) — ohne Kundennamen. Filter: active (true/false), property_id, limit.

GET /api/v1/meters/<meter_number>/readings (Scope: zaehler)

Ablesungen eines Zählers (Wert, Datum, Verbrauch, is_estimated, Periode).

POST /api/v1/meter-readings (Scope: zaehler)

Einen Zählerstand erfassen. Idempotent pro (Zähler, Abrechnungsperiode) — ein vorhandener Stand wird aktualisiert; Verbrauch & Schätz-/Korrekturabgleich laufen wie im UI.

POST /api/v1/meter-readings
{ "meter_number": "A-100", "value": 1234.5, "reading_date": "2026-06-30", "is_estimated": false }
→ 201 { "id": 88, "value": 1234.5, "consumption": 42.0, "billing_period": "2025/26", … }

Ohne billing_period/billing_period_id wird die aktive Periode genutzt. reading_date akzeptiert ISO (JJJJ-MM-TT) oder TT.MM.JJJJ.

POST /api/v1/meter-readings/bulk (Scope: zaehler)

Bis zu 500 Ablesungen pro Anfrage: { "readings": [ {…}, … ] }. Antwort: saved_count, error_count, saved, errors (pro Zeile mit Index).

Massen-Import (REST-only, PII-haltig)

Import-Endpunkte verarbeiten Klar-Stammdaten (Namen/Adressen) und sind daher bewusst nicht als MCP-Tool verfügbar.

POST /api/v1/import/customers (Scope: stammdaten)

POST /api/v1/import/properties (Scope: stammdaten)

POST /api/v1/import/meters (Scope: zaehler | stammdaten)

Body: { "rows": [ {…}, … ], "update_existing": false }. Upsert je natürlichem Schlüssel (Kundennummer / Objektnummer / Zählernummer). Maximal 1000 Zeilen pro Anfrage (413 too_many_rows, kein stilles Kürzen — größere Mengen in Blöcken senden). Jede Zeile ist transaktional isoliert; Fehler einer Zeile verwerfen nur diese. Antwort: created, updated, skipped, error_count, errors.

Beispiel-Felder: - customers: name (oder first_name/last_name), customer_number, email, strasse, hausnummer, plz, ort, is_company, externe_kennung. - properties: object_number, object_type (Default Haus), strasse, hausnummer, plz, ort. - meters: meter_number, property_id oder property_object_number (beim Anlegen Pflicht), location, meter_type (main/sub), initial_value, eichjahr.


Changelog

v1 — 2026-06-30

  • added Erstveröffentlichung: geocode, network/features(.geojson), meters, meters/<nr>/readings, meter-readings (+/bulk), import/{customers,properties,meters}, stats.