REST / JSON¶
Die REST-Schnittstelle ist der Zugriffsweg für alle Sprachen ohne offizielles SDK. Sie ist produktiv verfügbar — deckt aber nicht den vollen gRPC-Funktionsumfang ab. Was fehlt, steht unter Grenzen des REST-Wegs; lies das, bevor du dich auf REST als einzigen Zugriffsweg festlegst.
Die Referenz unter openapi.live.zedal.de wird direkt aus dem laufenden System erzeugt und ist damit immer aktuell; sie zeigt die Operationen, die die Umgebung anbietet, nach Diensten und Gruppen geordnet. Das OpenAPI-Dokument selbst (OpenAPI 3.0.3) liegt unter openapi.live.zedal.de/openapi.yaml und kann direkt in Codegeneratoren wie openapi-generator oder NSwag geladen werden.
Aufrufform¶
Die Pfade folgen dem vollqualifizierten gRPC-Methodennamen:
Beispiel:
POST /infotech.zedal.bxa.diwass.v1.api.CompanyDocumentApi/CreateSubmission HTTP/1.1
Host: openapi.test.zedal.de
Authorization: Bearer <access-token>
provider_id: <provider_id>
Content-Type: application/json
{ "…": "…" }
provider_id ist bei jedem Aufruf Pflicht
Der Header provider_id gehört an jede Anfrage — auch an den Login, der noch gar kein
Token trägt. Fehlt er, antwortet das Gateway mit HTTP 501 und einem Körper wie
{"code":12,"message":"Bad gRPC response. HTTP status code: 404"}. Diese Meldung nennt weder
den Header noch das Wort „Provider” und führt zuverlässig in die falsche Richtung.
Dieselbe Antwort bekommst du bei einer gültigen, aber umgebungsfremden Provider-ID: Die
IDs sind pro Umgebung verschieden, und DiscoveryApi/ListProvider liefert auf jedem Host das
übergreifende Verzeichnis — eine dort gelistete ID ist also nicht automatisch auf dem Host
gültig, den du gerade ansprichst.
Merkregel für die Fehlersuche: 501 heißt fast immer provider_id, nicht „Operation nicht
implementiert”.
Das HTTP-Verb ist nicht immer POST. Es stammt aus der google.api.http-Annotation der jeweiligen Proto-Methode; in der Praxis kommen POST, GET, PATCH, DELETE und PUT vor. Lesende Operationen sind überwiegend GET, mit den Parametern in der Query-String. Welches Verb für eine Operation gilt, steht in der Referenz — rate es nicht aus dem Methodennamen.
Ressourcenpfade, nicht Ressourcen-Design
Die Pfade sind bewusst RPC-förmig und nicht ressourcenorientiert. Es gibt kein /notifications/{id} — die Operation heißt so, wie die gRPC-Methode heißt. Jede REST-Operation entspricht damit genau einer gRPC-Methode. Umgekehrt gilt das nicht: Es gibt gRPC-Methoden ohne REST-Route (siehe Grenzen des REST-Wegs).
Authentifizierung¶
Drei Stufen, identisch für REST und gRPC:
sequenceDiagram
participant App as Deine Anwendung
participant Z as ZEDAL
App->>Z: ListProvider (ohne Token, ohne provider_id)
Z-->>App: Provider-Verzeichnis
App->>Z: IssueRefreshToken (Credentials + App-Token)
Z-->>App: Refresh-Token
App->>Z: RequestAccess (Bearer Refresh-Token)
Z-->>App: Access-Token + NEUES Refresh-Token
Note over App,Z: Das alte Refresh-Token ist damit ungültig
App->>Z: Fachlicher Aufruf (Bearer Access-Token)
| Schritt | Operation | Authorization | provider_id |
|---|---|---|---|
| 0 — Provider ermitteln | DiscoveryApi/ListProvider |
— | — |
| 1 — Login | AuthApi/IssueRefreshToken |
— | erforderlich |
| 2 — Access holen | AuthApi/RequestAccess |
Bearer Refresh-Token | erforderlich |
| 3 — Fachaufruf | jede weitere Operation | Bearer Access-Token | erforderlich |
DiscoveryApi/ListProvider ist die einzige Operation, die weder ein Token noch provider_id
verlangt. Sie ist damit der natürliche Startpunkt: erst das Verzeichnis abfragen, dann mit der
passenden Provider-ID in den Login.
Der Login trägt die UsernameCredentials des Accounts und zusätzlich den App-Token, den du bei der Registrierung erhältst. Mit den API-Keys, die folgen, überspringen Prozesse ohne Benutzer Login und Refresh: Sie tauschen in RequestAccess einen API-Key mit dem App-Token (apiKeyCredentials) direkt gegen ein Access-Token, ohne Refresh-Token — siehe Erste Schritte → Prozesse ohne Benutzer.
Token-Rotation — jeder RequestAccess invalidiert das alte Refresh-Token
Schritt 2 gibt immer ein neues Refresh-Token zurück, und das bisherige verliert seine Gültigkeit. Deine Anwendung muss das neue Token persistieren, bevor sie den nächsten Fachaufruf macht. Wer das ursprüngliche Token weiterverwendet, ist nach dem ersten Refresh ausgesperrt und muss sich neu anmelden.
Für parallel laufende Prozesse heißt das: eine Stelle refresht, alle anderen nutzen das Ergebnis. Zwei gleichzeitige RequestAccess-Aufrufe mit demselben Refresh-Token invalidieren sich gegenseitig.
Einzelne Operationen verlangen zusätzlich einen Scope (etwa usermgt für die Verwaltungsoperationen der AuthApi). Scopes hängen am App-Token und an der Subscriber-Zuordnung und stecken als Claim im Access-Token — sie werden nicht pro Aufruf angefordert.
Fehler¶
gRPC-Statuscodes werden auf HTTP-Statuscodes abgebildet; der Antwortkörper trägt die Fehlerdetails. Für die Fehlerbehandlung sind zwei Ebenen zu unterscheiden:
- Transportebene — HTTP-Status und gRPC-Status. Sagt, ob der Aufruf angekommen und zulässig war.
- Fachebene — Ablehnungen aus DiWaSS mit eigenen Fehlercodes wie
WASTE_CLASSIFICATION_INVALID_CODE. Ein solcher Fehler kann in einer HTTP-200-Antwort stecken, weil der Aufruf technisch erfolgreich war. Siehe Fehlercodes.
Grenzen des REST-Wegs¶
Nicht jede gRPC-Methode hat eine REST-Route. Die Routen entstehen aus den google.api.http-Annotationen der Protos — was dort nicht annotiert ist, existiert über REST nicht. Über REST lassen sich Transportdokumente speichern (DocumentApi/ImportTransportDocument) und Bestand, Eingang und Muster auflisten (QueryApi/ListTransportDocuments, ListInboxDocuments, ListTransportTemplates); ein Beispiel steht in der BMU-Anleitung. Es fehlt eine Gruppe, die für Integrationen zentral ist:
| Fehlt über REST | Folge |
|---|---|
alle übrigen Import…-Operationen der DocumentApi, darunter ImportTransportDocumentFromInbox und ImportDocumentToFileAreaFromInbox |
eingehende Vorgänge können nicht übernommen, Dokumente nicht in Akten importiert werden |
SendDocument, DeliverDocument |
kein ausdrücklicher Versand an einen Empfänger, keine Berechtigungsvergabe |
GetTransferDetails, FindFilesToDocument |
kein Status einer Übertragung, keine Suche nach Akten zu einem Dokument |
CreateNewFile, GrantAccessToFile, MergeFiles |
Akten-Operationen der FileApi |
QueryApi/ListFileAreaDocuments, ListFiles |
Dokumente im Nachweis-Bereich und Akten lassen sich nicht auflisten |
Eine rein REST-basierte Integration kann Eingänge nicht übernehmen
Der Inbox-Import ist der Zustellweg für Änderungen, die von außen kommen — Behörden-Nachforderungen, Entscheidungen, Antworten auf Änderungsanträge. Ohne ihn erscheinen diese Versionen nicht in deinem Dokumentenbestand, egal wie oft du nachlädst.
Über REST lässt sich der Eingang auflisten (QueryApi/ListInboxDocuments, DocumentApi/ListInbox) und aufräumen (SetInboxDocumentsRead, RemoveDocumentsFromInbox), aber nicht übernehmen: ImportTransportDocumentFromInbox und ImportDocumentToFileAreaFromInbox haben keine REST-Route. Eine Integration ohne gRPC-Anteil ist deshalb funktional unvollständig: Sie kann Dokumente erzeugen, einreichen und als Transportdokument speichern, aber die Antworten darauf nicht in den Bestand übernehmen.
Für den vollen Umfang: .NET SDK oder gRPC direkt. Mehr zum Zustellweg unter Aufrufe und Synchronisation.
Streaming¶
Der REST/JSON-Pfad ist unary — eine Anfrage, eine Antwort. Server-Streaming steht über REST nicht zur Verfügung, auch nicht als Server-Sent Events.
Wer Streaming braucht, hat zwei Möglichkeiten: natives gRPC, oder gRPC-Web über denselben openapi.*-Host — dort werden Server-Streaming-Antworten inkrementell ausgeliefert.
Header¶
Eigene HTTP-Header werden als gRPC-Metadata weitergegeben. Für die Korrelation deiner Aufrufe setzt du clientName und clientVersion als HTTP-Header, mit denselben Werten, die du bei der Registrierung angegeben hast; sie landen in den serverseitigen Logs.
Zwei Header sind für jeden Aufruf relevant:
| Header | Wann | Fehlt er |
|---|---|---|
Authorization: Bearer <token> |
ab Schritt 2 der Authentifizierung | 401 |
provider_id: <id> |
immer, auch beim Login | 501 mit gRPC-Code 12 |
Codegenerierung¶
Das OpenAPI-Dokument ist vollständig und selbsttragend — Client-Codegeneratoren kommen damit ohne Nacharbeit zurecht:
openapi-generator generate \
-i https://openapi.live.zedal.de/openapi.yaml \
-g python \
-o ./zedal-client
Pinne für produktive Builds eine heruntergeladene Kopie des Dokuments statt direkt gegen die URL zu generieren — sonst ändert sich dein generierter Client mit dem nächsten Plattform-Release.
Weiter¶
- Beispiele — lauffähiger Python-Code: anmelden, Eingang abfragen
- Erste Schritte — Zugang, erster Aufruf
- Feldregeln — was das Schema nicht sagt
- gRPC — Streaming und Type-Safety