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 habenrule.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.