Grundlagen
Fehler
Ein Envelope für jeden Fehler. Der Code ist der Vertrag, die Meldung ist Prosa, und der abgelehnte Wert kommt nie zurück.
Der Envelope
Jede Antwort mit einem Status außerhalb von 2xx hat diese Form — auch die von einem vorgeschalteten Proxy erzeugten nicht, weshalb ein Client die Struktur prüfen und nicht annehmen sollte.
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Die Eingaben sind ungültig.",
"details": [
{ "field": "recipients[0].email", "code": "invalid_string", "message": "Die E-Mail-Adresse hat kein gültiges Format." },
{ "field": "title", "code": "too_big", "message": "Es sind höchstens 200 Zeichen erlaubt." }
],
"requestId": "0193f8d2-6c4b-7a91-b8e2-5c1d9f0a3b47"
}
}Zwei Regeln, auf die Sie sich verlassen können
Der Code ist der Vertrag. Verzweigen Sie auf error.code. Die Meldung ist generisches Deutsch für Menschen und darf in jedem Release umformuliert werden; ein Client, der auf sie prüft, bricht bei einem Redaktionsdurchlauf.
Nichts, was hereinkam, geht zurück. Keine E-Mail-Adresse, kein Dateiname, kein Zugangscode, kein Dokumentinhalt — nicht in der Meldung, nicht in details. Ein details-Eintrag nennt den Feldpfad, einen stabilen Grundcode und eine Begründung, die ausschließlich aus dem Schema abgeleitet ist. Ein abgelehntes Passwort taucht deshalb nirgends auf.
Status und Codes
| Status | Codes | Bedeutung |
|---|---|---|
| 400 | VALIDATION_FAILED, BAD_REQUEST, TOKEN_INVALID | Die Anfrage ist so nicht verarbeitbar. details nennt die Felder. |
| 401 | UNAUTHENTICATED | Kein, unbekannter, widerrufener, abgelaufener oder für diese IP nicht zugelassener Schlüssel. Ein einziger Code für alle Fälle, damit die Antwort nicht verrät, welcher es war. |
| 403 | FORBIDDEN, SCOPE_INSUFFICIENT, STEP_UP_REQUIRED, PLAN_LIMIT_REACHED | Authentifiziert, aber nicht berechtigt. SCOPE_INSUFFICIENT behebt man im Schlüssel, FORBIDDEN in der Rolle. |
| 404 | NOT_FOUND | Existiert nicht — oder nicht in dieser Organisation. Die beiden Fälle sind absichtlich nicht unterscheidbar. |
| 409 | CONFLICT, INVALID_STATE_TRANSITION, IDEMPOTENCY_IN_PROGRESS | Der aktuelle Zustand lässt das nicht zu: ein bereits versandter Auftrag, ein doppelter Wert, eine noch laufende gleiche Anfrage. |
| 410 | REQUEST_EXPIRED, TOKEN_EXPIRED, TOKEN_ALREADY_USED | War gültig, ist es nicht mehr. Ein erneuter Versuch ändert daran nichts. |
| 412 | PRECONDITION_FAILED | Eine Voraussetzung fehlt — typisch beim Versand, wenn readiness nicht leer ist. |
| 413 / 415 | PAYLOAD_TOO_LARGE, UNSUPPORTED_MEDIA_TYPE | Die Datei ist zu groß oder das Format wird nicht unterstützt. |
| 422 | UPLOAD_REJECTED, MALWARE_DETECTED, CONVERSION_FAILED, IDEMPOTENCY_KEY_REUSED | Verstanden, aber nicht ausführbar. IDEMPOTENCY_KEY_REUSED heißt: derselbe Schlüssel, ein anderer Body. |
| 429 | RATE_LIMITED | Zu viele Anfragen. Retry-After nennt die Wartezeit in Sekunden. |
| 500 / 503 | INTERNAL_ERROR, SERVICE_UNAVAILABLE, PROVIDER_UNAVAILABLE | Unser Problem. Wiederholbar; bei einem Schreibzugriff nur mit demselben Idempotency-Key. |
Behandeln
import { Signido, isSignidoApiError } from '@signido/sdk';
const signido = new Signido({ apiKey: process.env.SIGNIDO_API_KEY });
try {
await signido.signatureRequests.send(requestId);
} catch (error) {
if (!isSignidoApiError(error)) throw error;
// Auf den Code verzweigen, nie auf die Meldung.
switch (error.code) {
case 'PRECONDITION_FAILED':
// readiness aus GET /signature-requests/{id} sagt, was fehlt.
break;
case 'RATE_LIMITED':
await new Promise((resolve) => setTimeout(resolve, (error.retryAfterSeconds ?? 60) * 1000));
break;
case 'SIGNATURE_LEVEL_UNAVAILABLE':
// Kein Fallback auf SES — das wäre eine andere Zusage als die getroffene.
break;
default:
// requestId gehört in jedes Support-Ticket und in jede Logzeile.
console.error(error.code, error.requestId);
}
// Für ein Formular: Feldpfad → Meldung.
const fields = error.fieldErrors();
}