Zum Inhalt

REST-API-v1-Referenz

Die v1-API unter /api/v1/ ist die unterstützte Schnittstelle für externe Integrationen. Sie ist auf günstiges Polling, stabile Identifikatoren und ein Wire-Format pro Konzept ausgelegt. Alles auf dieser Seite unterliegt den Kompatibilitätsregeln: Ergänzungen sind nicht-brechend, und Ihr Client muss unbekannte Felder ignorieren.

Die Authentifizierung ist unverändert: API-Key im Header X-API-Key (oder ein JWT-Bearer-Token). Alle Ressourcen sind auf Ihre Organisation beschränkt.

Konventionen

Envelope. Erfolgreiche Antworten sind {"data": …}; paginierte Sammlungen ergänzen "meta": {"total", "limit", "offset"}. Es gibt keine success-Flags.

Fehler. Jeder Fehler ist application/problem+json (RFC 9457):

{
  "type": "https://expecto.dev/problems/run-already-active",
  "title": "Conflict",
  "status": 409,
  "detail": "Another run is already active for this session",
  "code": "RUN_ALREADY_ACTIVE",
  "active_run_id": "…"
}

Verzweigen Sie auf den type-Slug (oder die code-Erweiterung) — beide sind stabil. Anfragen, die der aktuelle Zustand einer Ressource nicht erlaubt, sind 409 invalid-state; Validierungsprobleme sind 422 validation-error mit einem errors[]-Array der betroffenen Felder.

Paginierung. ?limit= (Default 100, max. 1000) und ?offset=, mit einer stabilen, dokumentierten Sortierreihenfolge pro Sammlung.

Idempotenz. POST /v1/sessions und POST /v1/sessions/{sid}/runs akzeptieren einen Idempotency-Key-Header. Ein Retry mit demselben Key und Body liefert das erste Ergebnis zurück, statt ein Duplikat zu erzeugen; derselbe Key mit anderem Body ist ein 409 idempotency-key-reuse. Keys verfallen nach 24 Stunden.

Gleichzeitige Sitzungs-Lesezugriffe. Das Lesen einer Sitzung lädt deren vollständigen Kontext, daher hat ein API-Key höchstens 4 solcher Anfragen gleichzeitig offen: GET /v1/sessions/{sid}, dessen Unterressourcen context und files sowie die Legacy-Endpunkte GET /api/sessions. Eine Anfrage über dem Limit wartet bis zu 15 Sekunden auf einen freien Platz und erhält dann 429 mit Retry-After. Parallelisieren Sie nicht breiter als das Limit: Ein Client, der pro Sitzung einer Listenseite eine Anfrage stellt, läuft ins Limit. Für interaktive Nutzer der Web-Anwendung gilt die Beschränkung nicht.

Das Ressourcenmodell

Session  (dünner Container einer Lieferung)
└── Run  (eine Pipeline-Ausführung — die zentrale Ressource)
    ├── report     leichte Validierungszusammenfassung
    └── findings   vereinheitlichte Verstöße aller Stufen

Die minimale Integration sind fünf Aufrufe:

POST /v1/sessions                 → Lieferung anlegen
PUT  /v1/sessions/{sid}/files/{role}   × je Datei
POST /v1/sessions/{sid}/runs      → Pipeline ausführen
     (Webhook oder günstiges Polling)
GET  /v1/sessions/{sid}/runs/latest/report
GET  /v1/sessions/{sid}/runs/latest/findings   — wenn der Report Fehler zeigt

Sessions

POST /v1/sessions

{
  "data_contract": {"external_id": "FIBU-STD-01"},
  "data_provider": {"external_id": "PM-1162"},
  "period": "2026-07",
  "context": {}
}

data_contract und data_provider akzeptieren {"id": "<uuid>"} oder {"external_id": "…"} — External IDs gehören Ihnen, sind pro Organisation eindeutig und ersparen UUID-Mappings auf Ihrer Seite. Optional: data_contract.revision, deadline, supplier_id. Antwort: 201 mit der dünnen Session und einem Location-Header.

GET /v1/sessions/{sid} — das Polling-Ziel

Liefert ~1 KB: Identifikatoren, status, health, period, latest_run-Zusammenfassung und links zu den Subressourcen. Nichts Schweres ist eingebettet.

Die Antwort trägt ein ETag. Pollen Sie mit If-None-Match: <etag> — eine unveränderte Session antwortet 304 mit leerem Body; Polling im Sekundentakt ist unproblematisch.

Subressourcen

Endpoint Zweck
GET /v1/sessions/{sid}/context Der Session-Kontext (inkl. Kontexttabellen). Nur hier werden Tabellen ausgeliefert: eine Sitzungs-Liste gibt ausschließlich skalare Kontextwerte zurück.
PATCH /v1/sessions/{sid}/context Kontext ersetzen: {"context": {…}}.
GET /v1/sessions/{sid}/files Upload- und Parse-Status je Rolle + missing_required_roles.
PUT /v1/sessions/{sid}/files/{role} Datei einer Quellrolle hochladen und ersetzen — ein idempotenter Aufruf, kein separates Delete. Unbekannte Rolle → 404. Multipart-Body, Feld file.

POST /v1/sessions/{sid}/decision

Eine Ressource für die menschliche Entscheidung über eine Lieferung:

{"action": "approve", "comment": "Salden geprüft"}

action ist confirm, approve oder reject. comment ist bei reject Pflicht (der Ablehnungsgrund) und dient bei approve als Override-Begründung, wenn noch blockierende Verstöße vorliegen. Eine im aktuellen Status unzulässige Entscheidung ist ein 409 invalid-state-Problem.

Runs

POST /v1/sessions/{sid}/runs

Die Pipeline auszuführen heißt, einen Run zu erzeugen:

{"stages": ["parse"], "source": "salesforce"}

Beide Felder sind optional. Ohne stages läuft die volle Pipeline ab dem aktuellen Stand der Session; ["parse"] allein ergibt den schnellen Zwei-Phasen-Ablauf (technische Fehler in Sekunden, Inhalte später). source ist ein frei wählbares Herkunftslabel am Run.

Antwort: 202 mit Location: /v1/sessions/{sid}/runs/{rid} und dem Run-Body. Solange ein Run aktiv ist, antwortet ein zweiter POST mit 409 run-already-active — ein Retry mit demselben Idempotency-Key liefert stattdessen das ursprüngliche 202 erneut.

Läuft der automatische Kontextabruf des Projekts noch, wenn der Run angefordert wird, wird der Run angenommen (202, Status pending) und das Parsen startet sofort — session.parsed oder session.failed (stage = parse) kommt innerhalb von Sekunden. Danach wartet der Run vor der Transformation: Status waiting, waiting_for: "context_fetch", executed_stages: ["parse"]. Er läuft von selbst weiter, sobald der Abruf abgeschlossen ist — unter derselben Run-ID. Schlägt der Abruf fehl oder dauert das Warten länger als RUN_WAIT_TIMEOUT_SECONDS des Servers (Standard 30 min, gezählt ab Anlage des Runs), wird der Run als errors geschlossen und session.failed gesendet (reason context_fetch_failed / context_fetch_timeout). Ein zweiter POST während des Wartens ergibt 409 run-already-active mit der ID des wartenden Runs.

GET /v1/sessions/{sid}/runs und GET …/runs/{rid}

Historie (neueste zuerst, paginiert) und die Run-Ressource: ein status-Verdikt (pending / waiting / running / passed / warnings / errors; die ersten drei sind nicht final), waiting_for (context_fetch, solange der Run wartet, sonst null), requested_stages vs. executed_stages, Ergebnisse und Timings je Stufe in stages, trigger {channel, source}, error + error_class, wenn der Run außerhalb der Daten scheiterte.

Das Literal latest wird überall akzeptiert, wo {rid} steht — nach einem Webhook lesen Sie /runs/latest/report, ohne eine Run-ID aufzulösen.

Ergebnisse

GET /v1/sessions/{sid}/runs/{rid}/report — „Datei 1"

Die leichte Validierungszusammenfassung, verfügbar sobald der Run beendet ist (vorher 409 run-not-finished): Zähler plus ein Eintrag je ausgewerteter Regel (rule_id, name, stage, outcome, level, error_class, total_findings, message). Beispielzeilen nur mit ?include=samples. session_id und run_id stehen immer im Body.

GET /v1/sessions/{sid}/runs/{rid}/findings

Ein Datensatzformat für die Verstöße aller Stufen:

{
  "finding_id": "1f0c5a…",
  "stage": "check",
  "rule": {"id": "8ec5e7d9-…", "name": "BR-3 Balance Reconciliation"},
  "severity": "error",
  "class": "content",
  "message": "Pro Konto und Objekt: …",
  "location": {"role": "buchungen", "row": 17, "column": "Saldo"},
  "data": {"sap_account": "0000300080", "balance_current": "1000.00"}
}
  • stage — parse (Dateiformat), validate (Entity-Regeln), check (Projekt-Geschäftsregeln).
  • rule.id — die stabile UUID; filtern mit ?rule_id=. Regelnamen sind Anzeigetexte, nie Identifikatoren. Parse-Checks haben rule.id: null.
  • data — genau die Spalten, die das SELECT der Regel erzeugt hat.
  • Filter: ?stage=, ?rule_id=, ?severity=, ?class=; Standard- Paginierung; stabile Sortierung (stage, rule, row, finding_id).

Progressive Verfügbarkeit: Findings einer abgeschlossenen Stufe sind lesbar, während der Run spätere Stufen noch ausführt — ?stage=parse direkt nach dem session.parsed-Webhook, Sekunden nach dem Start.

GET …/findings/summary liefert aggregierte Zähler je Stufe und Regel ohne Einzeldatensätze.

CSV

Senden Sie Accept: text/csv an /report oder /findings, und dieselben Daten kommen synchron als CSV zurück — mit session_id und run_id als Spalten in jeder Zeile. Ein Einzelregel-Export (?rule_id=…) flacht die Spalten der Regel aus („Datei pro Regel"). Oberhalb der Zeilenobergrenze antwortet die Anfrage mit einem Problem-Detail, das auf den asynchronen Export verweist — nie mit einer abgeschnittenen Datei.

Webhooks oder Polling?

Beides wird unterstützt und ist günstig: Webhook-Ereignisse (siehe Webhooks-Referenz) tragen data.run_id, und die dünne Session mit ETag macht kurzes Polling harmlos. Wählen Sie, was zu Ihrer Laufzeitumgebung passt.