AI translation
This page was translated from German with AI. If in doubt, the German original applies.
REST / JSON¶
The REST API is the access path for all languages without an official SDK. It is available in production — but does not cover the full gRPC feature set. What is missing is described under Limits of the REST path; read that before you commit to REST as your only access path.
The reference at openapi.live.zedal.de is generated directly from the running system and is therefore always up to date: 131 operations in 21 services, arranged in 7 groups (Core, DIWASS, BXA-DIWASS, BXA-BMU, Signer, Notifier, Utility). The OpenAPI document itself (OpenAPI 3.0.3) is available at openapi.live.zedal.de/openapi.yaml and can be loaded directly into code generators such as openapi-generator or NSwag.
Call format¶
The paths follow the fully qualified gRPC method name:
Example:
POST /infotech.zedal.bxa.diwass.v1.api.CompanyDocumentApi/CreateSubmission HTTP/1.1
Host: openapi.test.zedal.de
Authorization: Bearer <access-token>
provider_id: 106
Content-Type: application/json
{ "…": "…" }
provider_id is mandatory on every call
The provider_id header belongs on every request — including the login, which does not
carry a token yet. If it is missing, the gateway responds with HTTP 501 and a body like
{"code":12,"message":"Bad gRPC response. HTTP status code: 404"}. This message mentions neither
the header nor the word “provider” and reliably points you in the wrong direction.
You get the same response with a valid provider ID that belongs to a different environment:
the IDs differ per environment, and DiscoveryApi/ListProvider returns the cross-environment
directory on every host — so an ID listed there is not automatically valid on the host you are
currently addressing.
Rule of thumb for troubleshooting: 501 almost always means provider_id, not “operation not
implemented”.
The HTTP verb is not always POST. It comes from the google.api.http annotation of the respective proto method; in practice, POST, GET, PATCH, DELETE and PUT occur. Read operations are mostly GET, with the parameters in the query string. Which verb applies to an operation is stated in the reference — do not guess it from the method name.
Resource paths, not resource design
The paths are deliberately RPC-style rather than resource-oriented. There is no /notifications/{id} — the operation is named the way the gRPC method is named. Each REST operation therefore corresponds to exactly one gRPC method. The reverse does not hold: there are gRPC methods without a REST route (see Limits of the REST path).
Authentication¶
Three stages, identical for REST and gRPC:
sequenceDiagram
participant App as Your application
participant Z as ZEDAL
App->>Z: ListProvider (without token, without provider_id)
Z-->>App: Provider directory
App->>Z: IssueRefreshToken (credentials + app token)
Z-->>App: Refresh token
App->>Z: RequestAccess (Bearer refresh token)
Z-->>App: Access token + NEW refresh token
Note over App,Z: This invalidates the old refresh token
App->>Z: Business call (Bearer access token)
| Step | Operation | Authorization | provider_id |
|---|---|---|---|
| 0 — Determine the provider | DiscoveryApi/ListProvider |
— | — |
| 1 — Login | AuthApi/IssueRefreshToken |
— | required |
| 2 — Get access | AuthApi/RequestAccess |
Bearer refresh token | required |
| 3 — Business call | every other operation | Bearer access token | required |
DiscoveryApi/ListProvider is the only operation that requires neither a token nor provider_id.
It is therefore the natural starting point: query the directory first, then log in with the
matching provider ID.
Two credential types are available for login: UsernameCredentials for user-bound applications and ClientIdCredentials for automated processes. Both also require the app token you receive at registration.
Token rotation — every RequestAccess invalidates the old refresh token
Step 2 always returns a new refresh token, and the previous one loses its validity. Your application must persist the new token before it makes the next business call. If you keep using the original token, you are locked out after the first refresh and have to log in again.
For processes running in parallel, this means: one component refreshes, all others use the result. Two simultaneous RequestAccess calls with the same refresh token invalidate each other.
Some operations additionally require a scope (for example usermgt for the administrative operations of the AuthApi). Scopes are tied to the app token and the subscriber assignment and are carried as a claim in the access token — they are not requested per call.
Errors¶
gRPC status codes are mapped to HTTP status codes; the response body carries the error details. For error handling, distinguish between two levels:
- Transport level — HTTP status and gRPC status. Tells you whether the call arrived and was permitted.
- Business level — rejections from DIWASS with their own error codes such as
WASTE_CLASSIFICATION_INVALID_CODE. Such an error can be contained in an HTTP 200 response, because the call was technically successful. See Error codes.
Limits of the REST path¶
Not every gRPC method has a REST route. The routes are created from the google.api.http annotations of the protos — whatever is not annotated there does not exist via REST. Via REST you can store transport documents (DocumentApi/ImportTransportDocument) and list inventory, inbox and templates (QueryApi/ListTransportDocuments, ListInboxDocuments, ListTransportTemplates); an example is in the BMU guide. Missing is a group that is central to integrations:
| Missing via REST | Consequence |
|---|---|
all other Import… operations of the DocumentApi, including ImportTransportDocumentFromInbox and ImportDocumentToFileAreaFromInbox |
incoming processes cannot be taken over, documents cannot be imported into files |
SendDocument, DeliverDocument |
no explicit sending to a recipient, no granting of permissions |
GetTransferDetails, FindFilesToDocument |
no status of a transfer, no search for files related to a document |
CreateNewFile, GrantAccessToFile, MergeFiles |
file operations of the FileApi |
QueryApi/ListFileAreaDocuments, ListFiles |
documents in the file area and files cannot be listed |
A purely REST-based integration cannot take over incoming documents
The inbox import is the delivery path for changes that come from outside — requests for additional information from authorities, decisions, responses to requests for change. Without it, these versions do not appear in your document inventory, no matter how often you reload.
Via REST you can list the inbox (QueryApi/ListInboxDocuments, DocumentApi/ListInbox) and clean it up (SetInboxDocumentsRead, RemoveDocumentsFromInbox), but not take entries over: ImportTransportDocumentFromInbox and ImportDocumentToFileAreaFromInbox have no REST route. An integration without a gRPC part is therefore functionally incomplete: it can create and submit documents and store them as transport documents, but cannot take the responses to them over into the inventory.
For the full scope: the .NET SDK or gRPC directly. More on the delivery path under Calls and synchronization.
Streaming¶
The REST/JSON path is unary — one request, one response. Server streaming is not available via REST, not even as Server-Sent Events.
If you need streaming, you have two options: native gRPC, or gRPC-Web via the same openapi.* host — there, server streaming responses are delivered incrementally.
Headers¶
Incoming HTTP headers are passed through to the backend as gRPC metadata, minus the hop-by-hop and framing headers (connection, host, content-length, content-type, te, transfer-encoding, upgrade, proxy-connection, user-agent, accept*, grpc-encoding, grpc-accept-encoding, grpc-timeout).
There is no allowlist and no prefix scheme: if you set a custom header, expect it to reach the backend. This is the intended way to correlate across service boundaries.
Two headers are relevant for every call:
| Header | When | If missing |
|---|---|---|
Authorization: Bearer <token> |
from step 2 of authentication | 401 |
provider_id: <id> |
always, including at login | 501 with gRPC code 12 |
Code generation¶
The OpenAPI document is complete and self-contained — client code generators handle it without any rework:
openapi-generator generate \
-i https://openapi.live.zedal.de/openapi.yaml \
-g python \
-o ./zedal-client
For production builds, pin a downloaded copy of the document instead of generating directly against the URL — otherwise your generated client changes with the next platform release.
Next¶
- Samples — runnable Python code: log in, query the inbox
- Getting started — access, first call
- Field rules — what the schema does not tell you
- gRPC — streaming and type safety