Zum Inhalt

Erste Schritte

Diese Seite bringt dich vom Null-Stand bis zum ersten erfolgreichen Aufruf gegen ZEDAL — einmal über REST/JSON und einmal über das .NET SDK. Beide Wege führen auf dieselben Backend-Methoden und benutzen dasselbe Auth-Modell; du brauchst nur einen davon.

Weg Wann Abschnitt
REST / JSON Jede Sprache mit HTTP-Client. Kein Codegen, kein SDK. Erster Aufruf über REST
.NET SDK C#-Anwendungen. Kapselt Token-Refresh, Dokument-Lebenszyklen, Retries. Erster Aufruf über das .NET SDK

Wer natives gRPC generieren will, findet die Grundlagen unter API-Referenz → gRPC; der Auth-Ablauf auf dieser Seite gilt dort unverändert.

Voraussetzungen und Zugang

Für einen Aufruf brauchst du vier Dinge:

Angabe Was es ist Woher
Provider Numerische provider_id der ZEDAL-Instanz, auf der dein Account liegt. Sie gilt für genau eine Umgebung. aus der Zugangs-Mail; die Liste liefert DiscoveryApi/ListProvider, siehe Schritt 0
Subscriber + Account + Passwort Vertrags-Mandant und Benutzerkonto darin. ZEDAL-Support
App-Token JWT, der deine Anwendung identifiziert und ihr Berechtigungs-Profil festlegt. Er bildet die Rechtetabelle aus dem Portal ab — was der App-Token nicht freischaltet, kann der Account über die API nicht tun. ZEDAL-Support
Umgebung Test / Preview / Production, siehe Umgebungen. deine Entscheidung

App-Tokens und Account-Daten werden nicht öffentlich verteilt. Schreib eine Mail an erp-tools@zedal.de. Damit die Anfrage in einem Durchgang beantwortet werden kann, nenne darin:

  1. Integrierendes Unternehmen — Name und Anschrift.
  2. Anwendung — Name und Version. Beide gehen als clientName / clientVersion in jeden Aufruf ein und tauchen so in den serverseitigen Logs auf; wähle Werte, die du dauerhaft behalten willst.
  3. Ziel-Umgebung — Test, Preview oder Production. Für den Einstieg: Test.
  4. Provider — falls schon bekannt, der Provider-Kurzname oder die provider_id; sonst „bitte zuweisen”.
  5. Fachlicher Umfang — welche Rollen und Vorgänge du bedienen willst (z. B. DiWaSS-Notifizierung und Begleitformulare aus Unternehmenssicht, oder Behördensicht). Daraus wird das Berechtigungs-Profil des App-Tokens abgeleitet.
  6. Art des Logins — interaktiver Benutzer-Login (UsernameCredentials) oder Prozess ohne Benutzer. Für Prozesse ohne Benutzer folgen API-Keys; du legst sie dann im ZEDAL-Portal unter „Mein ZEDAL“ → „API-Keys“ für den Account an.
  7. Ansprechpartner — technisch und fachlich.

Du bekommst zurück: App-Token, Subscriber, Account, Initialpasswort und die provider_id.

Umgebungen

Umgebung Zweck Nativ gRPC REST / JSON und gRPC-Web
Production Produktivbetrieb api.live.zedal.de openapi.live.zedal.de
Preview Staging- und Beta-Systeme api.preview.zedal.de openapi.preview.zedal.de
Test Test- und Integrationssysteme api.test.zedal.de openapi.test.zedal.de

Entwickle gegen Test. Diese Umgebung ist für Integrationen vorgesehen und wird als solche betrieben. Preview ist der Vorschau-Stand vor einem Produktiv-Release; dort testest du sinnvoll erst, wenn deine Integration gegen Test läuft.

Jede Umgebung bedient nur ihre eigenen Provider. Eine provider_id, die auf Test funktioniert, wird auf Production nicht bedient und umgekehrt — auch dann nicht, wenn DiscoveryApi/ListProvider sie auf beiden Hosts auflistet: Die Discovery-Liste ist ein umgebungsübergreifendes Verzeichnis, kein Routing-Versprechen. Maßgeblich ist die provider_id, die dir für diese Umgebung genannt wurde.

Das Auth-Modell

Der Ablauf ist dreistufig und für REST, gRPC und SDK identisch.

sequenceDiagram
    participant App as Deine Anwendung
    participant Z as ZEDAL

    App->>Z: AuthApi/IssueRefreshToken<br/>appToken + Credentials
    Z-->>App: refreshToken
    Note over App: refreshToken sicher ablegen

    App->>Z: AuthApi/RequestAccess<br/>Authorization: Bearer refreshToken
    Z-->>App: accessToken + newRefreshToken
    Note over App,Z: Das vorgelegte refreshToken ist ab hier ungültig.<br/>newRefreshToken persistieren, bevor es weitergeht.

    App->>Z: Fachlicher Aufruf<br/>Authorization: Bearer accessToken
    Z-->>App: Ergebnis

    App->>Z: AuthApi/RevokeRefreshToken (Logout)
Stufe Operation Authentifizierung Ergebnis
1 — Login AuthApi/IssueRefreshToken keine (appToken + Credentials im Body) refreshToken
2 — Access holen AuthApi/RequestAccess Authorization: Bearer <refreshToken> accessToken und newRefreshToken
3 — Fachaufruf jede weitere Operation Authorization: Bearer <accessToken> —
Logout AuthApi/RevokeRefreshToken Authorization: Bearer <refreshToken> Session beendet

Der Login trägt neben dem App-Token die usernameCredentials mit subscriberId, accountId und password. Mit den API-Keys, die folgen, brauchen Prozesse ohne Benutzer den Login nicht mehr: Sie tauschen den Key direkt gegen ein Access-Token.

Gültigkeit

Token Erneuerung Woher du die Gültigkeit erfährst
Access-Token über RequestAccess mit dem aktuellen Refresh-Token — kein erneuter Login exp-Claim im Token
Refresh-Token jede Verwendung liefert ein neues Token exp-Claim im Token

Keine Lebensdauer im Client fest annehmen

Beide Token sind JWTs und tragen ihre Ablaufzeit im exp-Claim. Die konkrete Dauer wird serverseitig beim Ausstellen gesetzt und kann sich ändern — lies sie aus dem Token, statt sie im Client zu hinterlegen. Eine hart kodierte Annahme führt entweder zu abgelaufenen Aufrufen oder zu unnötigen Refreshes.

Im .NET SDK steht die Ablaufzeit in Jwt.ValidTo. Das SDK erneuert das Access-Token von selbst, sobald weniger als fünf Minuten Restlaufzeit bleiben.

Solange dein Prozess das Refresh-Token vor dessen Ablauf verwendet, bleibt die Session bestehen. Die ursprünglichen Credentials brauchst du danach nicht mehr und sollst sie in der Anwendung nicht dauerhaft speichern.

Token-Rotation — hier scheitern die meisten Integrationen

Jeder RequestAccess entwertet das vorgelegte Refresh-Token

Stufe 2 gibt immer ein neues Refresh-Token zurück (newRefreshToken), und das eingereichte verliert im selben Moment seine Gültigkeit. Persistiere das neue Token, bevor du den nächsten Fachaufruf machst.

Symptom, wenn du es nicht tust: Der erste Durchlauf funktioniert, der zweite scheitert an Stufe 2 mit HTTP 401 und {"code":16,"message":"The token '…' is invalid."}. Von außen sieht das aus wie ein abgelaufenes Token — tatsächlich ist es ein überschriebenes.

Parallele Prozesse sperren sich gegenseitig aus

Zwei Prozesse, die dasselbe gespeicherte Refresh-Token benutzen, invalidieren sich beim Refresh wechselseitig: Der zweite RequestAccess legt ein Token vor, das der erste bereits verbraucht hat. Beide fallen früher oder später auf HTTP 401, und ein Neustart hilft nicht — das gespeicherte Token ist tot, es hilft nur ein neuer Login mit Credentials.

Zwei tragfähige Muster:

  • Ein Refresher. Genau eine Stelle hält die Session und erneuert; alle anderen Prozesse bekommen von ihr das aktuelle Access-Token. Innerhalb eines Prozesses reicht ein Lock um den Refresh.
  • Eine Session pro Prozess. Jeder Prozess loggt sich einmal eigenständig ein und führt sein eigenes Refresh-Token in eigenem Storage. Sessions laufen unabhängig voneinander.

Was nicht funktioniert: dasselbe Refresh-Token in mehreren Prozessen oder auf mehreren Maschinen.

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.

Prozesse ohne Benutzer: API-Key

API-Keys folgen

API-Keys stehen noch nicht bereit. Dieser Abschnitt beschreibt sie vorab; Zedal.Sdk unterstützt sie ab einem Release nach 1.3.0. Wann API-Keys für deine Umgebung bereitstehen, erfährst du beim ZEDAL-Support (erp-tools@zedal.de).

Ein Prozess ohne Benutzer — Integration, ERP-Anbindung, Skript — braucht weder Login noch Rotation. Er tauscht einen API-Key zusammen mit dem App-Token in AuthApi/RequestAccess direkt gegen ein Access-Token. Ein Refresh-Token entsteht dabei nicht.

sequenceDiagram
    participant App as Deine Anwendung
    participant Z as ZEDAL

    App->>Z: AuthApi/RequestAccess<br/>apiKeyCredentials: apiKey + appToken
    Z-->>App: accessToken + accessTokenExpiresOn + notGrantedScopes
    Note over App: Kein Refresh-Token. Der Key ändert sich nicht.

    App->>Z: Fachlicher Aufruf<br/>Authorization: Bearer accessToken
    Z-->>App: Ergebnis

    Note over App,Z: Kurz vor Ablauf des Access-Tokens denselben Key erneut tauschen.
Login mit Credentials API-Key
Access-Token holen IssueRefreshToken, dann RequestAccess mit dem Refresh-Token RequestAccess mit apiKeyCredentials, ohne Authorization-Header
Erneuern RequestAccess mit dem jeweils neuesten Refresh-Token RequestAccess erneut mit demselben Key
Dauerhaft speichern das rotierende Refresh-Token den Key
Scopes im Access-Token aus App-Token und Subscriber-Zuordnung nur die, die Key und App-Token haben
Beenden RevokeRefreshToken nichts; den Key selbst widerrufst du im Portal

API-Keys legst du im ZEDAL-Portal unter „Mein ZEDAL“ → „API-Keys“ an, für den dort angemeldeten Account; über die API lassen sie sich nicht anlegen. Zum Anlegen fragt das Portal noch einmal das Passwort des Accounts ab, denn der Key gilt über die Sitzung hinaus. Beim Anlegen wählst du die Scopes, die der Key gewähren darf, und optional ein Ablaufdatum. Zur Wahl stehen core, dataexport und wastemanager, und nichts ist implizit: Ein Key ohne Scope weist nur den Account aus. Widerrufen wird ebenfalls im Portal. Danach gibt es mit dem Key kein neues Access-Token mehr; bereits ausgegebene können bis zu ihrem Ablauf gültig bleiben.

Ein API-Key ist kein JWT, sondern eine Zeichenkette der Form zedal_ak_<provider_id>_<key_id>_<geheimnis>. Am festen Präfix erkennen Secret-Scanner einen Key, der versehentlich in ein Repository oder ein Log gerät. Die provider_id darin ist die des Providers, der den Key ausgegeben hat, also der Wert für den Header provider_id. Account, Scopes und Ablauf stehen nicht im Key; die kennt nur ZEDAL. Das Portal zeigt den Key genau einmal. Bewahre ihn auf wie ein Passwort.

Jeder Account legt Keys nur für sich an, auch der Hauptaccount. Der Hauptaccount sieht im Portal zusätzlich die Keys seiner Benutzer und kann sie widerrufen. Ein Key handelt mit den Rechten seines Accounts. Soll er nicht am Zugang einer Person hängen, leg ihn an einem eigenen Account für die Integration an.

Die Antwort von RequestAccess nennt in accessTokenExpiresOn, wann das Access-Token abläuft, und in notGrantedScopes die Scopes des App-Tokens, die ein Key gewähren könnte, dieser aber nicht. Die gewährten Scopes stehen im scope-Claim des Access-Tokens.

Den Key nicht vor jedem Aufruf tauschen

Der Server begrenzt, wie oft ein Key in einer Zeitspanne getauscht werden kann. Darüber antwortet RequestAccess mit HTTP 429 (gRPC-Status RESOURCE_EXHAUSTED). Verwende das Access-Token, bis accessTokenExpiresOn naht, und tausche erst dann.

Ein unbekannter, widerrufener oder abgelaufener Key ergibt HTTP 401 (UNAUTHENTICATED), ebenso ein gesperrter Account.

Erster Aufruf über REST

Basis-URL ist der openapi.*-Host der gewählten Umgebung, der Pfad ist der vollqualifizierte gRPC-Methodenname:

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

Die vollständige Liste aller 131 Operationen steht in der API-Referenz; Details zum Aufrufformat unter REST / JSON.

Der Header provider_id ist bei jedem Aufruf Pflicht

Alle Provider einer Umgebung teilen sich denselben API-Host. Ohne provider_id kann die Plattform den Aufruf keiner Instanz zuordnen — auch nicht beim Login.

Symptom: HTTP 501 mit dem Körper {"code":12,"message":"Bad gRPC response. HTTP status code: 404","details":[]}. Der Text legt einen falschen Pfad nahe; tatsächlich lässt sich der Aufruf nicht zuordnen. Zwei Ursachen kommen infrage: Der Header fehlt — oder er nennt einen Provider, den diese Umgebung nicht bedient.

Einzige Ausnahme ist DiscoveryApi/ListProvider — die Operation ist providerübergreifend und unauthentifiziert.

Die folgenden Schritte laufen mit curl und jq gegen die Test-Umgebung. Setz zuerst die Basiswerte:

export ZEDAL_HOST=https://openapi.test.zedal.de
export ZEDAL_APP_TOKEN='<app token from the access email>'
export ZEDAL_PASSWORD='<account password>'

Schritt 0: Provider ermitteln

Der einzige Aufruf, der ohne Token und ohne provider_id funktioniert. Er ist zugleich der schnellste Konnektivitätstest.

curl -s "$ZEDAL_HOST/infotech.zedal.provider_discovery.v1.api.DiscoveryApi/ListProvider" \
  | jq '.provider[] | {id, shortName, type}'
{ "id": 1, "shortName": "PROD", "type": "PROD" }
{ "id": 2, "shortName": "TEST", "type": "TEST" }

Setz die id, die dir für diese Umgebung genannt wurde:

export ZEDAL_PROVIDER='<your provider_id>'

Die vollständige Antwort führt pro Provider zusätzlich name, vendor, portalUrl, soapUrl und grpcUrl.

Schritt 1: Login — Refresh-Token holen

REFRESH=$(curl -s -X POST \
  "$ZEDAL_HOST/infotech.zedal.core.v1.api.AuthApi/IssueRefreshToken" \
  -H 'Content-Type: application/json' \
  -H "provider_id: $ZEDAL_PROVIDER" \
  -d '{
        "appToken": "'"$ZEDAL_APP_TOKEN"'",
        "usernameCredentials": {
          "subscriberId": "mysubscriber",
          "accountId":    "myaccount",
          "password":     "'"$ZEDAL_PASSWORD"'"
        }
      }' \
  | jq -r .refreshToken)

Mit einem API-Key, sobald es ihn gibt, überspringt ein Prozess ohne Benutzer diesen Schritt und Schritt 2, siehe Variante: Access-Token mit API-Key.

Schritt 2: Access-Token holen

RESPONSE=$(curl -s -X POST \
  "$ZEDAL_HOST/infotech.zedal.core.v1.api.AuthApi/RequestAccess" \
  -H 'Content-Type: application/json' \
  -H "provider_id: $ZEDAL_PROVIDER" \
  -H "Authorization: Bearer $REFRESH" \
  -d "{\"refreshToken\": \"$REFRESH\"}")

ACCESS=$(echo "$RESPONSE"  | jq -r .accessToken)
REFRESH=$(echo "$RESPONSE" | jq -r .newRefreshToken)   # mandatory: the old one is now dead

Das Refresh-Token steht sowohl im Authorization-Header als auch im Body-Feld refreshToken. Beide werden erwartet; ausgewertet und rotiert wird das Feld aus dem Body.

Die letzte Zeile ist der Punkt, an dem eine Integration steht oder fällt: In einer echten Anwendung gehört dorthin ein Schreibvorgang in denselben Speicher, aus dem der nächste Prozessstart das Token liest — nicht eine Shell-Variable.

Variante: Access-Token mit API-Key

Folgt mit den API-Keys

Diese Variante steht zur Verfügung, sobald die API-Keys bereitstehen.

Mit einem API-Key ersetzt ein einziger Aufruf die Schritte 1 und 2. RequestAccess bekommt Key und App-Token im Body; einen Authorization-Header braucht es nicht.

export ZEDAL_API_KEY='<API-Key aus dem Portal, zedal_ak_…>'

ACCESS=$(curl -s -X POST \
  "$ZEDAL_HOST/infotech.zedal.core.v1.api.AuthApi/RequestAccess" \
  -H 'Content-Type: application/json' \
  -H "provider_id: $ZEDAL_PROVIDER" \
  -d '{
        "apiKeyCredentials": {
          "apiKey":   "'"$ZEDAL_API_KEY"'",
          "appToken": "'"$ZEDAL_APP_TOKEN"'"
        }
      }' \
  | jq -r .accessToken)

ZEDAL_PROVIDER ist hier die Zahl hinter zedal_ak_ im Key. Die Antwort enthält kein Refresh-Token; zu speichern gibt es nichts außer dem Key. Weiter geht es mit Schritt 3.

Schritt 3: Fachlicher Aufruf

DocumentApi/ListInbox listet die Dokument-IDs im Posteingang. Ein GET mit den Filtern in der Query-String — das Verb steht pro Operation in der Referenz und ist nicht immer POST.

curl -s -G "$ZEDAL_HOST/infotech.zedal.core.v1.api.DocumentApi/ListInbox" \
  -H "provider_id: $ZEDAL_PROVIDER" \
  -H "Authorization: Bearer $ACCESS" \
  --data-urlencode 'from=2026-01-01T00:00:00Z' \
  --data-urlencode 'to=2026-12-31T23:59:59Z' \
  --data-urlencode 'unread.value=true'
{ "docIds": [ { "id": "…" }, { "id": "…" } ] }

Ohne Filter listet die Operation alle ungelesenen Dokumente ohne Zeitgrenze. Zeitstempel sind RFC 3339 in UTC. Verschachtelte Felder werden in der Query-String mit Punkt geschrieben (unread.value), so wie in der Referenz angegeben.

Fehler lesen

Der Antwortkörper hat immer die Form {"code": <grpc-status>, "message": "…", "details": []}. Der HTTP-Status leitet sich aus dem gRPC-Status ab.

HTTP Körper Ursache
501 {"code":12,"message":"Bad gRPC response. HTTP status code: 404"} provider_id-Header fehlt, oder der genannte Provider wird von dieser Umgebung nicht bedient
400 {"code":3,"message":"MISSING_CREDENTIALS"} im Login-Body fehlen die usernameCredentials
401 {"code":16,"message":"The token '…' is invalid."} Token abgelaufen, rotiert, widerrufen — oder gar kein JWT
401 {"code":16,"message":"The given authentication token is invalid."} Authorization-Header fehlt ganz

Das ist die Transportebene. Fachliche Ablehnungen aus DiWaSS haben eigene Fehlercodes und können in einer HTTP 200-Antwort stecken, weil der Aufruf technisch erfolgreich war — siehe Fehlercodes.

Aufrufe wiederauffindbar machen

Das .NET SDK schickt bei jedem Aufruf die Header clientName und clientVersion mit; sie landen in den serverseitigen Logs. Über REST setzt du sie als HTTP-Header. Nimm dieselben Werte, die du bei der Registrierung angegeben hast; das verkürzt jede Supportanfrage erheblich.

Erster Aufruf über das .NET SDK

Installation

dotnet add package Zedal.Sdk

Target ist netstandard2.0; die Versionierung folgt Semantic Versioning (1.3.0). Das Paket zieht die generierten gRPC-Typen transitiv mit.

Client anlegen

using Zedal.Sdk;
using Zedal.Sdk.Impl;   // TargetSystem
using Zedal.Sdk.Jwt;    // AppToken, RefreshToken

var client = ZedalClientFactory.Default.CreateClient(
    system:        TargetSystem.Test,      // Production | Preview | Test
    clientName:    "MyIntegrationApp",     // appears server-side as the caller
    clientVersion: "1.0.0");

Adressen setzt das SDK aus TargetSystem selbst; provider_id hängt es an jeden Aufruf an, sobald die Session steht. Beides musst du nicht selbst verwalten.

Transport — meist nichts zu tun

Der Parameter transport steht per Default auf ZedalTransport.Auto: Beim Aufbau der Verbindung wird einmal geprüft, ob der native Host HTTP/2 spricht. Nur bei erwiesener HTTP/1.x-Beschränkung schaltet das SDK auf ZedalTransport.GrpcWeb über den openapi.*-Host um; bleibt die Prüfung ohne Antwort (offline, Timeout), bleibt es bei nativem gRPC. Die Prüfung ist ein blockierender Netzwerkaufruf im Konstruktor.

In Umgebungen, in denen HTTP/2 sicher blockiert ist — alte Corporate-Proxies, .NET Framework —, kannst du den Umweg sparen und transport: ZedalTransport.GrpcWeb direkt setzen. Läuft der Verkehr über einen Proxy, gib ihn über den Parameter proxyUrl oder die Umgebungsvariable INFOTECH_GRPC_PROXY_URL an.

Rotations-Callback registrieren — vor dem Login

SetAccessTokenChanged wird bei jeder Token-Rotation aufgerufen: nach CreateSession, nach ContinueSession und bei jedem automatischen Refresh.

client.SetAccessTokenChanged(tokens =>
    SecureStorage.Save("zedal-refresh-token", tokens.refreshToken.Jwt.EncodedToken));

tokens ist ein Tupel (AccessToken accessToken, RefreshToken refreshToken). Die kodierte JWT-Zeichenkette liegt unter Jwt.EncodedToken; alternativ liefert ToByteArray() dieselbe Zeichenkette als UTF-8-Bytes.

SecureStorage steht in den Beispielen stellvertretend für deinen geschützten Speicher — DPAPI, Keychain, ein Secret-Store. Das Refresh-Token erlaubt, beliebig neue Access-Tokens auszustellen; behandle es wie ein Passwort.

Ohne diesen Callback überlebt die Session keinen Neustart

Das SDK rotiert das Refresh-Token bei jedem Refresh. Wenn du nur das Token aus dem Login speicherst, ist es beim nächsten Programmstart längst entwertet — ContinueSession scheitert dann mit RpcException, Status Unauthenticated, und du brauchst wieder die Credentials.

Registriere den Callback vor CreateSession und vor ContinueSession: Beide rotieren selbst schon, und ein danach registrierter Callback verpasst genau diese erste Rotation.

Login und erster Aufruf

await client.CreateSession(
    providerId:   int.Parse(Environment.GetEnvironmentVariable("ZEDAL_PROVIDER")),
    subscriberId: "mysubscriber",
    accountId:    "myaccount",
    password:     Environment.GetEnvironmentVariable("ZEDAL_PASSWORD"),
    appToken:     new AppToken(Environment.GetEnvironmentVariable("ZEDAL_APP_TOKEN")));

var docIds = await client.CoreApi.Documents.ListInbox(
    from:   DateTime.UtcNow.AddDays(-30),
    to:     DateTime.UtcNow,
    unread: true);

foreach (var docId in docIds)
{
    Console.WriteLine(docId.Id);
}

Das ist derselbe Aufruf wie Schritt 3 im REST-Beispiel. Zwei Details:

  • from und to müssen UTC sein. Ein DateTime mit Kind ungleich Utc lässt die Protobuf-Konvertierung mit ArgumentException scheitern, bevor überhaupt ein Aufruf rausgeht.
  • Access-Tokens erneuert das SDK selbsttätig, sobald weniger als fünf Minuten Restlaufzeit bleiben. Du musst RequestAccess nicht selbst aufrufen.

Neben der Variante mit getrennten Feldern gibt es CreateSession(userId, appToken) mit dem kombinierten Muster <subscriber>-<account>@<provider>:<password>. Der Login mit API-Key, CreateSession(apiKey, appToken), folgt mit den API-Keys. Details in der .NET-SDK-Doku.

Neustart: Session fortsetzen

var client = ZedalClientFactory.Default.CreateClient(
    system:        TargetSystem.Test,
    clientName:    "MyIntegrationApp",
    clientVersion: "1.0.0");

// Callback first — ContinueSession already rotates.
client.SetAccessTokenChanged(tokens =>
    SecureStorage.Save("zedal-refresh-token", tokens.refreshToken.Jwt.EncodedToken));

var refreshToken = new RefreshToken(SecureStorage.Load("zedal-refresh-token"));
await client.ContinueSession(refreshToken);

Kein Credential-Login, keine Passwörter im Speicher. ContinueSession liest die provider_id aus dem Token; du musst sie nicht erneut angeben.

Logout

await client.EndSession();

Beendet die Session serverseitig und macht das Refresh-Token unbrauchbar. Danach ist nur noch ein Login mit Credentials möglich — lösch das gespeicherte Token bei dieser Gelegenheit mit.

Timeouts

Der Client-Default-Timeout liegt bei 30 Sekunden. Für Aufrufe, die serverseitig an DiWaSS weitergereicht werden, ist das zu knapp; setz die Zeitgrenze pro Aufruf:

await client.DiwassApi.AttachmentApi.GetAttachment(attachmentIdentifier,
    ZedalCallContext.Default.WithTimeout(600));

ZedalCallContext steuert Deadline, CancellationToken und zusätzliche Header. WithoutDeadline() schaltet die Zeitgrenze ganz ab — sinnvoll nur zusammen mit einem CancellationToken. Eine gerissene Deadline kommt als RpcException mit StatusCode.DeadlineExceeded zurück und sieht aus wie ein Serverfehler, ist aber keiner. Konkrete Richtwerte je Vorgang stehen bei den DiWaSS-Anleitungen.

Wie es weitergeht

  • DiWaSS-Anleitungen — grenzüberschreitende Abfallverbringung: Notifizierung, Begleitformulare, Behördensicht
  • REST / JSON — Aufrufform, Header-Durchreichung, Streaming-Grenzen, Codegenerierung
  • Feldregeln — Formate und Wertebereiche, die das Schema nicht ausdrückt
  • Fehlercodes — was eine fachliche Ablehnung bedeutet und was zu tun ist
  • Codelisten — zulässige Werte für kontrollierte Felder