Zum Inhalt

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.

API-Referenz öffnen

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:

<HTTP verb> https://openapi.<environment>.zedal.de/<service.FullName>/<method>

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.

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