API-Leitfaden (v1)

Versionierte, öffentliche API. Referenz: ADR-0019. Interaktiv ausprobieren: /docs/api. Maschinenlesbar: GET /api/v1/openapi.json.

Basis

https://www.landnutzen.at/api/v1

Pfad-versioniert (/api/v1). Breaking Changes → /api/v2; neue Felder werden additiv innerhalb v1 ergänzt (Contract bleibt stabil).

Authentifizierung und Report-Quota

Die API funktioniert ohne Key. Der Reportpfad unterscheidet drei strikt getrennte Modi:

ModusWieTageslimit
Anonymkein Key und keine sicher auflösbare bestehende Kontositzung10 pro pseudonymem Request-IP-Bucket
Sessionbestehende gleich-originäre Browser-Sitzung eines vorhandenen Users10 pro pseudonymem Account-Bucket
API-KeyAuthorization: Bearer ln_… oder X-API-Key: ln_…eigenes in der Datenbank hinterlegtes Limit

Der Session-Modus verwendet ausschließlich die vorhandene LandNutzen- Browser-Sitzung. Er ist kein neues Cookie-Auth-Verfahren für headless oder Cross-Origin-Clients; diese verwenden weiterhin einen API-Key. Kann eine Sitzung nicht sicher aufgelöst werden oder existiert der zugehörige User nicht mehr, gilt der anonyme IP-Bucket; eine Sessionstörung überspringt die Bucket-Prüfung nicht.

Ein angebotener API-Key wird immer vor einer Sitzung geprüft. Gespeichert wird nur ein SHA-256-Hash des Keys, nie der Klartext. Ein ungültiger oder widerrufener Key antwortet mit 401 und fällt weder auf Cookie-Auth noch auf anonymen Zugriff zurück. Die Key-Ausstellung erfolgt aktuell durch die Plattform (Self-Service folgt mit dem Admin-Dashboard).

Endpunkte

GET /api/v1/report

ParameterOrtPflichtBeispiel
addressQueryjaRathausplatz 1, 2230 Gänserndorf
curl "https://www.landnutzen.at/api/v1/report?address=Rathausplatz%201%2C%202230%20Gänserndorf"

POST /api/v1/report

curl -X POST https://www.landnutzen.at/api/v1/report \
  -H "Content-Type: application/json" \
  -d '{"address":"Rathausplatz 1, 2230 Gänserndorf"}'

Erfolg (200) = Report-JSON nach Report-Contract — kein Wrapper. Antwort- Header: X-RateLimit-Limit, X-RateLimit-Remaining, X-Auth-Mode. X-Auth-Mode ist anon, session oder key; Limit und Restwert gehören immer zum tatsächlich verwendeten Bucket. Report-Antworten bleiben Cache-Control: private, no-store.

Der Account-Bucket ist account:v1:<HMAC-SHA-256(AUTH_SECRET, report:account:v1:<users.id>)>. User-ID und E-Mail werden nicht im Rate-Limit gespeichert. Der Prefix trennt Konto-Buckets von anonymen IP- und API-Key-Buckets. Davon unabhängig bleibt reports.ip_hash das Pseudonym der tatsächlichen Request-IP. Bei einer Kontolöschung wird der Account-Bucket explizit entfernt; der allgemeine wöchentliche Prune begrenzt ältere Rate-Limit-Zeilen nach 90 Tagen. Eine interne FK-Lifecycle-Zuordnung bindet auch frühere HMAC-Generationen nach einer AUTH_SECRET-Rotation an das Konto und löscht sie beim User-Delete per Cascade. Details: Report-Quota-Vertrag.

Additiver Reportblock: historische Hitzebelastung

risiko.hitzebelastung ist ein optionaler additiver v1-Block. Er erscheint nur bei einem aktiven, qualifizierten Quellenrelease von geosphere-hora-hitzelayer und enthält historische mittlere Tage pro Jahr der Klimaperiode 1991–2020:

  • hitzetage_30c_jahr, extreme_hitzetage_35c_jahr, tropennaechte_20c_jahr, hitzewellentage_kysely_jahr;
  • vollständige Release-, Lizenz- und Rasterprovenienz einschließlich source_release_id, release_key, resolution_m: 1000, native_crs: EPSG:3416 und CC BY 4.0.

Kein aktives Release, keine konkrete Rasterabdeckung oder unvollständige Provenienz bedeuten: Der Block fehlt. Bestehende Reports bleiben kompatibel; es gibt weder einen HORA-Live-Provideraufruf noch eine Prognose oder Ampel. Der bestehende SPARTACUS-v3-Klima-Block zeigt unverändert das abgeschlossene Referenzjahr 2025. Vollständiger Quellen- und Reportvertrag.

Der Produktionsreadback belegt sowohl die aktive Quellenbasis als auch getrennt den öffentlichen, nicht persistierenden API-Consumer auf Merge 08d55c3. Das Fehlen des optionalen Blocks bleibt bei fehlender Rasterabdeckung oder einem fail-closed Release-/Rechte-Gate ein vertragskonformes Ergebnis.

Bedingter DXF-Export eines gespeicherten Reports

GET /api/v1/report/{id}/dxf ist kein allgemeiner Katasterdownload. Der Pfad wird nur dann im OpenAPI-Schema veröffentlicht, wenn parcel_dxf im Code release_bound und DXF_EXPORT_ENABLED exakt true ist. Produktion läuft seit dem nachgewiesenen #412-Cutover mit true. Der tatsächlich ausgeführte Rollback auf false ließ GET und OPTIONS mit 404 private, no-store antworten und entfernte den Pfad aus dem Schema. Flagwechsel werden auf Vercel erst nach einem Redeploy wirksam; Aktivierung, Rollback und finale Wiederaktivierung liefen deshalb auf demselben geprüften Commit jeweils bis Ready und wurden danach gegen Route plus OpenAPI rückgelesen. Das deploymentabhängige OpenAPI-Dokument wird mit Cache-Control: no-store ausgeliefert. Die Daten-Pointer blieben bei allen drei Zuständen unverändert.

Ein freigeschalteter Aufruf bleibt an den vorhandenen wertschöpfenden Report, seinen Public-Release, dieselbe Koordinate und den qualifizierten bev-parzellen/export-Pointer gebunden. Er exportiert vollständige, unveränderte Pilot-Originalpolygone in voller Stützpunktdichte als ASCII-R12- DXF in EPSG:31287. Sichtbare BEV-Attribution, Quelle, CC BY 4.0, Bearbeitungshinweis, Nicht-Unterstützung und Vermessungsgrenze sind Pflicht. Eigentümerdaten, ein eigenständiges BEV-Produkt, nationale Katasterabdeckung, Clipping oder Vereinfachung sind ausgeschlossen.

  • 200: qualifizierte DXF-Datei;
  • 400: ungültiges Report-ID-Format;
  • 403 report_context_mismatch: Der gespeicherte Report ist nicht an den erforderlichen wertschöpfenden Nicht-BEV- und Public-Release-Kontext gebunden;
  • 404: Report fehlt, ist abgelaufen oder das Flag ist aus;
  • 429: DXF-Tageslimit;
  • 503: Export-Release, Geometriezustand oder räumlicher Nutzungszweck passt nicht;
  • 500 dxf_failed: Der Renderer konnte die qualifizierte Geometrie nicht als DXF erzeugen; die Antwort bleibt private, no-store.

Der Consumer ist produktiv aktiviert. Der Produktionsnachweis belegt Dunkelzustand, positiven und negativen Live-Readback, GDAL-Parser, Pointerkonstanz, Rollback und finale Wiederaktivierung. NÖGIS #413 und ein eigenständiger Parzellenpass bleiben davon getrennt offen.

Fehler-Envelope

Stabil bei jedem Nicht-2xx:

{ "error": { "code": "address_missing", "message": "…" } }
StatuscodeBedeutung
400address_missing / invalid_jsonEingabe fehlt/ungültig
401unauthorizedAPI-Key ungültig oder widerrufen
429rate_limitedTageslimit erreicht (Retry-After)
500internalunerwarteter Fehler

Admin: Werkzeug-KPIs

GET /api/v1/admin/metrics erfordert einen API-Key mit admin-Scope und liefert ausschließlich die kennungsfreien Aggregate aus ADR-0042: das gewählte rollierende Tagesfenster sowie Status und Kennzahlen des festen 14-Tage-Ausgangswerts.

ParameterWerteStandard
days1, 7, 14, 3014
toolstabile öffentliche Tool-ID oder allall

Die Antwort enthält Funnel-, Qualitäts-, Laufzeit-, Kosten-, Regions- und Providerzusammenfassungen sowie den Tagesverlauf. baselineSnapshot wird davon getrennt während D+1 bis einschließlich D+14 nach dem gemeinsamen Funnel-/Server-Anker fortgeschrieben. Mit Ablauf von D+14 ist er vollständig und bleibt ab D+15 unverändert. Der Snapshot bewahrt die Funnel-, Fehler-, Provider-, Runtime-, Laufzeit- und Kostendimensionen einschließlich Zählern und Summen, enthält aber weder Tag noch Region und bleibt unabhängig von der 30-Kalendertage-Retention der Tageswerte erhalten. Es gibt keinen Rohereignis-Endpunkt. Antworten tragen Cache-Control: private, no-store. Ist die Aggregation nicht verfügbar, antwortet der Endpunkt ehrlich mit 503 metrics_unavailable statt mit einer leeren Erfolgsantwort.

Admin: Datenquellen und Drift-Policy

GET /api/v1/admin/data-sources erfordert einen API-Key mit admin-Scope. Der bestehende Quellenkatalog, letzte technische Lauf und aktive Release bleiben unverändert; DATA-02 ergänzt ausschließlich additive v1-Felder:

{
  "generatedAt": "2026-08-25T10:47:00.000Z",
  "monitoring": {
    "cronName": "monitor-data-source-drift",
    "lastCheckedAt": "2026-08-25T10:47:00.000Z",
    "lastRunId": 123,
    "lastAlertDelivery": { "status": "sent" },
    "monitoredSources": 9,
    "counts": { "fresh": 8, "due": 1, "stale": 0, "failed": 0, "unknown": 0 }
  },
  "sources": [
    {
      "slug": "basemap-orthofoto",
      "activePointer": { "releaseId": "…", "pointerVersion": 2 },
      "monitoring": {
        "releaseId": "…",
        "pointerVersion": 2,
        "state": "due",
        "severity": "warning",
        "findings": [{ "code": "freshnessDue" }],
        "thresholds": { "dueLeadMinutes": 120 },
        "alert": { "status": "sent" }
      }
    }
  ]
}

Das Beispiel ist ein Schemaausschnitt, kein Produktionsreadback. Legacy- Quellen ohne Active Pointer erhalten monitoring: null. Freigegebene Teilabdeckung und bekannte Warnungen bleiben eine akzeptierte Baseline; erst relevante Änderungen, veraltete Frische oder fehlende Evidenz erzeugen due, stale, failed oder unknown.

Alle Antworten tragen Cache-Control: private, no-store. Fehlt der Admin-Key, antwortet die Route mit 401; ein Key ohne admin-Scope erhält 403. Ist der Quellen- oder Historienvertrag nicht vollständig lesbar, lautet die Antwort 503 data_sources_unavailable; ein leerer oder erfunden gesunder Status wird nicht ausgegeben. Der vollständige Contract ist in GET /api/v1/openapi.json unter /admin/data-sources dokumentiert.

Ehrliche Datenlage

Nicht verfügbare oder unsichere Datenschichten stehen in jedem Report unter meta.degraded (Layer, Grund, Fallback). Die Widmung ist eine Indikation (nicht rechtsverbindlich), Kostenrahmen sind Grob-Schätzungen. Quellen/Lizenzen je Report unter sources — siehe Datenquellen & Lizenzen.