# QES für Plattformen mit vielen Signaturen je Person

Ihre Plattform lässt Personen mit eigenem qualifiziertem Zertifikat viele Dokumente signieren – Bestätigungen, Bescheinigungen, Verträge –, oft Dutzende am Tag und ohne dass die Person bei jeder Unterschrift vor dem Bildschirm sitzt. Dieser Guide beschreibt den Weg mit **automatischen Zertifikaten**: einmal einrichten, danach signiert Ihr System per API, und zwar nur im Rahmen der Einwilligung, die die Person selbst erteilt hat.

Grundlagen zu Mitgliedern, Zertifikaten und Freigabe-Links stehen im Guide [„QES mit Mitgliedern und Freigabe-Link“](/docs/guides/qes-members). Mit einem Test-Schlüssel (`qgw_test_…`) läuft jeder Schritt im Testmodus, ohne Kosten.

## Überblick

1. Mitglied anlegen – die Person, die signiert.
2. Automatisches Zertifikat bestellen; die Person identifiziert sich einmal.
3. Einwilligung zum automatischen Signieren anfragen; die Person legt den Rahmen fest.
4. Zugangsdaten hinterlegen und testen.
5. Signieren mit `certificateId` – einzeln oder im Stapel.
6. Ergebnis per Webhook empfangen, signiertes Dokument und Nachweis abrufen.

## 1. Mitglied anlegen

```http
POST /v1/members
Authorization: Bearer qgw_test_…
Idempotency-Key: mitglied-0815
Content-Type: application/json

{ "firstName": "Erika", "lastName": "Mustermann", "email": "erika.mustermann@example.com", "externalId": "person-0815" }
```

`externalId` ist Ihre Kennung der Person, damit Sie sie später zuordnen. Scope `members:manage`.

## 2. Automatisches Zertifikat bestellen

```http
POST /v1/certificates
Authorization: Bearer qgw_test_…
Idempotency-Key: zertifikat-0815
Content-Type: application/json

{ "memberId": "0192f1b0-0000-7000-8000-00000000010f", "kind": "AUTOMATIC" }
```

Scope `certificates:purchase`. Die Antwort enthält `identificationUrl`: Die Person identifiziert sich einmalig über diesen Link (sie bekommt ihn auch per E-Mail). Den Stand zeigt `GET /v1/certificates/{id}` (`lifecycleState`). Warten Sie hier nicht auf `certificate.activated`: Dieses Ereignis kommt erst nach dem erfolgreichen Test der Zugangsdaten in Schritt 4, der wiederum die Einwilligung aus Schritt 3 voraussetzt. Fragen Sie die Einwilligung gleich nach der Bestellung an.

## 3. Einwilligung anfragen

Ein automatisches Zertifikat signiert mit `certificateId` nur, wenn die Person zugestimmt hat. Ihre Plattform fragt die Einwilligung an, erteilen kann sie nur die Person selbst:

```http
POST /v1/certificates/0192f1b0-0000-7000-8000-00000000010e/consent-request
Authorization: Bearer qgw_test_…
Idempotency-Key: einwilligung-0815
Content-Type: application/json

{ "scope": { "channels": ["API", "BATCH"], "apiKeyIds": ["0192f1b0-0000-7000-8000-000000000201"], "dailyLimit": 200, "validUntil": "2027-12-31T23:00:00Z" } }
```

- `channels`: `API` für einzelne Signaturen per `POST /v1/signatures`, `BATCH` für Stapel per `POST /v1/signature-batches`. Nennen Sie nur die Kanäle, die Ihre Plattform nutzt.
- `apiKeyIds` (optional) beschränkt die Einwilligung auf die Schlüssel Ihrer Plattform.
- `dailyLimit` (optional): höchstens so viele Signaturen je Kalendertag (Berliner Zeit). Planen Sie das Limit mit Abstand zum üblichen Tagesvolumen.
- `validUntil` (optional): Ablaufdatum der Einwilligung.

Die Antwort ist immer 202 (`object: automatic_consent_request`). Die Person bekommt eine E-Mail mit einem persönlichen Link (72 Stunden gültig), erteilt oder lehnt dort ab und kann die Einwilligung jederzeit widerrufen oder sofort sperren. Änderungen meldet das Ereignis `certificate.consent_changed` (Scope `certificates:read`). Eine neue Anfrage ersetzt eine offene.

## 4. Zugangsdaten hinterlegen und testen

`PUT /v1/certificates/{id}/credentials` speichert Benutzername und PIN des Zertifikats verschlüsselt – nur, solange eine gültige Einwilligung besteht (sonst 403 / 9006 `automatic_consent_missing`). Danach prüft `POST /v1/certificates/{id}/test` die Zugangsdaten mit einer Testsignatur; ist der Test erfolgreich, ist das Zertifikat bereit und das Ereignis `certificate.activated` folgt. Im Testmodus hinterlegt `POST /v1/certificates/{id}/sandbox-credentials` die Zugangsdaten der Sandbox. Ab jetzt nennt Ihr System nur noch die `certificateId`; die PIN muss es nicht aufbewahren.

## 5. Signieren

### Einzelnes Dokument

```http
POST /v1/signatures
Authorization: Bearer qgw_test_…
Idempotency-Key: bestaetigung-0815
Content-Type: application/json

{
  "inputDocuments": [{ "sourceType": "base64", "payload": "<PDF als Base64>", "name": "bestaetigung-0815.pdf" }],
  "certificateId": "0192f1b0-0000-7000-8000-00000000010e",
  "signatureType": "pades",
  "options": { "level": "B" },
  "placement": { "page": 1, "x": 391, "y": 36, "width": 168, "height": 39 },
  "appearance": { "showDate": true, "reason": "Bestätigung 0815" },
  "externalId": "bestaetigung-0815",
  "metadata": { "vorgang": "0815" }
}
```

- `certificateId` wählt das Zertifikat. Benutzername und Passwort im Body (`certificateUsername`, `certificatePassword`) sind abgekündigt; neue Integrationen senden sie nicht.
- Ein Aufruf mit API-Schlüssel ist der Kanal `API`: Die Einwilligung muss ihn abdecken (und den Schlüssel, wenn sie `apiKeyIds` nennt). Sonst antwortet die API mit 403 / 9013 und `details.reason` (`automatic_consent_missing`, `automatic_channel_not_allowed`, `automatic_blocked`) und signiert nichts; über dem Tageslimit mit 409 / 9016 `automatic_daily_limit`.
- Base64-Dokumente sind bis 11 MB (Länge des Base64-Texts) möglich. Größere PDFs bis 50 MB laden Sie vorher mit `POST /v1/documents` oder `POST /v1/documents/import` hoch und senden `documentId` statt `inputDocuments`.
- `placement` und `appearance` erzeugen eine sichtbare Signatur (Seite, Rechteck in PDF-Punkten, Datum mit Zeitzone der Organisation, Grund bis 120 Zeichen); ohne `placement` ist die Signatur unsichtbar. Damit alles lesbar bleibt, wählen Sie das Rechteck mindestens so groß: nur Datum 140 × 40, Datum und Grund bis 40 Zeichen 180 × 60, Grund bis 120 Zeichen 260 × 80 PDF-Punkte (Einzelheiten im Guide „QES mit Mitgliedern und Freigabe-Link“, Abschnitt „Sichtbares Signaturbild“). Passt der Grund trotzdem nicht, wird er im Bild gekürzt und die Signatur meldet `options.appearance.reasonTruncated: true` (Antwort, `GET /v1/signatures/{id}`, Webhooks – auch für jedes Dokument eines Stapels); in der Signatur selbst steht er vollständig.
- `externalId` und `metadata` sind Ihre Referenzen: Sie stehen an der Signatur, kommen in jeder Antwort und jedem Webhook zurück und lassen sich filtern (`GET /v1/signatures?externalId=bestaetigung-0815`). An den Signaturdienst gehen sie nie.

### Ungültige Dokumente und Ablehnungen

- Ungültige Dokumente werden vor jedem Signaturversuch abgewiesen: 422 / 9012 mit `details.reason` (`invalid_base64`, `empty`, `not_pdf`, `unparseable`) und bei mehreren Dokumenten `details.index`. Dabei entstehen weder Kosten noch ein Datensatz; korrigieren Sie das Dokument und senden Sie es neu.
- Verschlüsselte PDFs gehen als Ganzes an den Signaturdienst; ist zum Öffnen ein Kennwort nötig, senden Sie es in `options.encryptionPassword`. Lehnt der Dienst ein verschlüsseltes PDF ohne `options.encryptionPassword` ab, antwortet die API 422 / 9019 mit `details.reason: encrypted`. Eine sichtbare Signatur (`placement`) lässt sich in ein verschlüsseltes PDF nicht einfügen: ohne `options.encryptionPassword` 422 / 9012 `encrypted`.
- Lehnt der Signaturdienst eine Anfrage ab, antwortet die API mit 422 / 9019 und `details.reason` (`signature_refused`, `timestamp_failed`, `encrypted` oder `rejected`). Dasselbe Wort steht danach als `error.reason` an der Signatur (`GET /v1/signatures/{id}`, `signature.failed`) – auch wenn die Ablehnung erst im Hintergrund kommt, etwa bei einem Stapel oder nach einer 202-Antwort.
- Fällt der Signaturdienst aus, nachdem die Anfrage gesendet wurde (Fehler, Zeitüberschreitung oder Verbindungsabbruch), ist das Ergebnis endgültig: synchron antwortet die API 503 / 9010 mit `QGW-Should-Retry: true`, die Signatur steht danach auf `REJECTED` mit `error.reason: service_failed` und `error.shouldRetry: true`. Kommt die Störung erst im Hintergrund (nach einer 202-Antwort), meldet `signature.failed` dasselbe, wenn bis etwa zehn Minuten nach dem Senden kein Ergebnis eingetroffen ist. Senden Sie das Dokument dann erneut als neue Anfrage mit einem neuen `Idempotency-Key`.

### Antwort, Wartezeit und Wiederholungen

- Der Aufruf antwortet in der Regel nach wenigen Sekunden mit 200 und `status: DONE`. Ist die Signatur nach 25 Sekunden nicht fertig (gezählt ab dem Eingang; die Vorbereitung großer Dokumente zählt mit und wird nicht abgebrochen) oder ist der Signaturdienst ausgelastet, antwortet die API mit 202 und `status: WAIT_SIGN` und dem Header `Retry-After`; die Signatur läuft weiter und wird von selbst abgeschlossen – das Ergebnis kommt per Webhook (`signature.completed` oder `signature.failed`), per `callback` und über `GET /v1/signatures/{id}`. Es wird nie ein zweites Mal signiert.
- Setzen Sie das Timeout Ihres HTTP-Clients auf mindestens 30 Sekunden. Der erste Aufruf nach längerer Ruhe kann länger dauern als die folgenden.
- Mit `Prefer: respond-async` bekommen Sie immer sofort 202 – sinnvoll, wenn Ihre Plattform viele Dokumente nacheinander schickt und nicht auf jede Signatur warten will.
- Dauerhaft je API-Schlüssel (Dashboard → Entwickler → API-Schlüssel → „Zustellung ändern“, Inhaberin oder Admin): `asyncDefault` beantwortet jede Signaturanfrage des Schlüssels sofort mit 202; `priority: bulk` („Massenversand“) reiht zusätzlich nach den interaktiven Anfragen ein und nutzt nur einen begrenzten Teil der Kapazität (nur absenkbar, zurück zu `interactive` geht nur mit einem neuen Schlüssel); `expiresSecondsDefault` (60 bis 86.400 Sekunden) ist die Frist in der Warteschlange, wenn die Anfrage kein `expiresInSeconds` nennt. `GET /v1/me` zeigt die drei Werte unter `apiKey`. Anfragen mit `certificateOtp` werden nie eingereiht.
- Senden Sie immer einen `Idempotency-Key` (zum Beispiel Ihre Dokumentnummer). Eine Wiederholung mit gleichem Schlüssel und gleichem Body liefert 24 Stunden lang die gespeicherte Antwort statt einer zweiten Signatur. 409 / 9008 heißt: Die erste Anfrage läuft noch – kurz warten und mit demselben Schlüssel wiederholen. Derselbe Schlüssel mit anderem Body antwortet 422 / 9008.
- Den Stand liest `GET /v1/signatures/{id}`; öfter als alle paar Sekunden abzufragen beschleunigt nichts. Besser: Webhook (Schritt 6).

### Viele Dokumente auf einmal: Stapel

Laden Sie die Dokumente hoch (`POST /v1/documents/import` mit `base64` oder `fileUrl`) und signieren Sie sie in einem Aufruf:

```http
POST /v1/signature-batches
Authorization: Bearer qgw_test_…
Idempotency-Key: stapel-2026-11-03
Content-Type: application/json

{
  "certificateId": "0192f1b0-0000-7000-8000-00000000010e",
  "title": "Bescheinigungen 3. November",
  "placement": { "page": "last", "x": 380, "y": 60, "width": 168, "height": 39 },
  "appearance": { "showDate": true, "reason": "Bescheinigung" },
  "documents": [
    { "documentId": "0192f1b0-0000-7000-8000-000000000103", "title": "Bescheinigung 0815" },
    { "documentId": "0192f1b0-0000-7000-8000-000000000104", "title": "Bescheinigung 0816" }
  ]
}
```

Die Antwort kommt sofort (202) mit einer Signatur-ID je Dokument; signiert wird im Hintergrund. Ein Stapel ist der Kanal `BATCH` und zählt jedes Dokument gegen das Tageslimit der Einwilligung. Wenn alles fertig ist, kommt `signature_batch.completed`; `GET /v1/signature-batches/{id}/archive` liefert alle signierten Dokumente als ZIP. Stapel müssen für Ihre Organisation freigeschaltet sein (sonst 403 / 9023).

#### Bis zu 250 Dokumente mit einer Freigabe

Ein Stapel nimmt bis zu 250 Dokumente (die Grenze Ihres Tarifs; Standard 50, mehr auf Anfrage) und wird mit **einem** Aufruf freigegeben, im Dashboard mit einer Bestätigung. Mehr als die Grenze: 422 / 9012 `batch_too_large` mit `details.max`.

- **Vorabprüfung vor der 202:** Reicht das Tageskontingent Ihrer Organisation für Stapel-Dokumente oder die Signaturkapazität für heute nicht für den ganzen Stapel, kommt 422 / 9012 mit `details.reason` `batch_budget` bzw. `batch_reserve`, `details.rejected` (`budget`, `reserve`), `details.requested` und `details.acceptable` (wie viele noch passen); angelegt wird nichts. Das Kontingent zählt alle Stapel des Tages, auch offene – viele kleine Stapel umgehen es nicht. Mit `"allowPartial": true` nimmt der Aufruf stattdessen die ersten `acceptable` Dokumente an (202, Feld `partial: { requestedItems, acceptedItems, rejected }`); die übrigen senden Sie später erneut. Der monatliche Kostendeckel gilt weiter für den ganzen Stapel (403 / 9017).
- **Reihenfolge:** Einzelne Signaturen gehen vor Stapeln; Stapel teilen sich die Kapazität im Wechsel je Organisation und je Zertifikat (höchstens einige Dokumente eines Zertifikats gleichzeitig). Richtwert: ein Stapel mit 250 Dokumenten ist im Normalbetrieb nach etwa 5 Minuten fertig, in Spitzenzeiten nach bis zu 15 Minuten.
- **Fortschritt:** Ihr Organisations-Webhook bekommt `signature_batch.progress` alle 25 fertigen Dokumente bzw. alle 10 % (der größere Schritt; bei 250 Dokumenten also neunmal), je Stand höchstens einmal, mit `doneCount`, `failedCount`, `returnedCount`, `openCount` und `finishedCount`. Am Ende kommt wie bisher `signature_batch.completed` mit den Teilerfolgen; `GET /v1/signature-batches/{id}` zeigt jederzeit den Stand.
- **Tagesfrist:** Ist für Ihre Installation eine Tagesfrist gesetzt (z. B. 23:30 Uhr), trägt der Stapel sie als `dailyDeadlineAt`. Dokumente, die bis dahin nicht an den Signaturdienst gesendet wurden, werden **unsigniert zurückgegeben** statt verspätet signiert: Zustand `REJECTED` mit 9009, `signature.failed` genau einmal, gezählt in `failedCount` und `returnedCount`, **ohne Kosten**. Ein Stapel, der nach der Frist kommt, wird mit 422 / 9012 `batch_daily_deadline` abgelehnt (`details.deadline`). Kontingent, Kapazitätsprüfung, Ablehnung nach der Frist und `allowPartial` gelten für alle Stapelarten (Dokumente, Einmal-Codes, Aufgaben; gezählt werden die Positionen); offene Positionen von Stapeln mit Einmal-Codes oder Aufgaben folgen ihrer Freigabe bzw. dem Vorgang und werden zur Frist nicht zurückgegeben.
- **Kosten:** Jedes signierte Dokument wird genau einmal berechnet; zurückgegebene, abgebrochene und gescheiterte Dokumente nicht. Im Testmodus entstehen keine Kosten.

## 6. Ergebnis empfangen

Ein Webhook-Abo meldet jedes Ergebnis, ohne dass Ihre Plattform abfragt:

```http
POST /v1/webhook-subscriptions
Authorization: Bearer qgw_test_…
Content-Type: application/json

{ "url": "https://example.com/hooks/signatures", "events": ["signature.completed", "signature.failed", "signature_batch.completed"] }
```

`signature.completed` enthält Ihre `externalId` und `metadata` sowie Links auf das signierte Dokument und den Nachweis. Abrufen können Sie beides auch direkt:

- `GET /v1/signatures/{id}/content` – das signierte PDF (mit `?format=base64` als JSON),
- `GET /v1/signatures/{id}/audit-trail` – das Nachweisprotokoll (JSON oder PDF über `Accept`); es nennt die signierende Person, den Kanal, den API-Schlüssel (Name und Präfix), das Zertifikat und bei automatischen Zertifikaten die Einwilligung, die beim Signieren galt – das PDF auf Deutsch und Englisch mit Ortszeit,
- `POST /v1/signatures/{id}/download-links` – ein Download-Link ohne API-Schlüssel, 15 Minuten gültig.

Wie Sie die Signatur der Webhooks prüfen, zeigt der Guide [„Webhooks empfangen und prüfen“](/docs/guides/webhooks).

## Hinweise

- Live-Signaturen beachten das monatliche Ausgabenlimit Ihrer Organisation (403 / 9017 bei hartem Limit).
- Im Live-Modus braucht die Einwilligung einen freigegebenen Einwilligungstext; bis dahin antwortet die Anfrage mit 409 / 9030.
- Zertifikate mit Einmalcodes (`kind: OTP`) eignen sich nicht für diesen Weg: Jede Signatur braucht den aktuellen Code der Person (`certificateOtp`), ohne ihn 422 / 9025 `otp_required`. Für solche Zertifikate gibt es den Freigabe-Link (Guide „QES mit Mitgliedern und Freigabe-Link“).
