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.

400 Bad Request
{
  "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

StatusCodesBedeutung
400VALIDATION_FAILED, BAD_REQUEST, TOKEN_INVALIDDie Anfrage ist so nicht verarbeitbar. details nennt die Felder.
401UNAUTHENTICATEDKein, 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.
403FORBIDDEN, SCOPE_INSUFFICIENT, STEP_UP_REQUIRED, PLAN_LIMIT_REACHEDAuthentifiziert, aber nicht berechtigt. SCOPE_INSUFFICIENT behebt man im Schlüssel, FORBIDDEN in der Rolle.
404NOT_FOUNDExistiert nicht — oder nicht in dieser Organisation. Die beiden Fälle sind absichtlich nicht unterscheidbar.
409CONFLICT, INVALID_STATE_TRANSITION, IDEMPOTENCY_IN_PROGRESSDer aktuelle Zustand lässt das nicht zu: ein bereits versandter Auftrag, ein doppelter Wert, eine noch laufende gleiche Anfrage.
410REQUEST_EXPIRED, TOKEN_EXPIRED, TOKEN_ALREADY_USEDWar gültig, ist es nicht mehr. Ein erneuter Versuch ändert daran nichts.
412PRECONDITION_FAILEDEine Voraussetzung fehlt — typisch beim Versand, wenn readiness nicht leer ist.
413 / 415PAYLOAD_TOO_LARGE, UNSUPPORTED_MEDIA_TYPEDie Datei ist zu groß oder das Format wird nicht unterstützt.
422UPLOAD_REJECTED, MALWARE_DETECTED, CONVERSION_FAILED, IDEMPOTENCY_KEY_REUSEDVerstanden, aber nicht ausführbar. IDEMPOTENCY_KEY_REUSED heißt: derselbe Schlüssel, ein anderer Body.
429RATE_LIMITEDZu viele Anfragen. Retry-After nennt die Wartezeit in Sekunden.
500 / 503INTERNAL_ERROR, SERVICE_UNAVAILABLE, PROVIDER_UNAVAILABLEUnser Problem. Wiederholbar; bei einem Schreibzugriff nur mit demselben Idempotency-Key.

Behandeln

Fehler auswerten
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();
}