# Dokumentprüfungen in einem Eingangs-Workflow

> Fügen Sie einem Eingangs-Workflow Dokumentprüfungen hinzu: hochladen, den Webhook empfangen, nach Risikostufe weiterleiten, Prüfung durch Menschen in die Warteschlange stellen, die Berichts-ID speichern und später verifizieren.

Quelle: https://fileverity.com/de/docs/intake-workflow

Diese Anleitung stellt Dokumentprüfungen vor einen Prozess, der Dokumente von außen entgegennimmt: Kreditanträge, Schadenmeldungen, Lieferanten-Onboarding oder Spesenabrechnungen. Jedes Dokument wird einmal geprüft, nach seiner Risikostufe weitergeleitet, und seine Berichts-ID wird als Audit-Nachweis aufbewahrt. Die API liefert eine Stufe und eine empfohlene Maßnahme. Ihre Richtlinie und die prüfenden Personen entscheiden.

Es verwendet das Modell `fvdoc-3` mit den Aufrufen aus [Dateien](https://fileverity.com/de/docs/files.md), [Analysen](https://fileverity.com/de/docs/analyses.md) und [Webhooks](https://fileverity.com/de/docs/webhooks.md). Mit einem Testschlüssel liefern Upload und Prüfung sofort ein Beispiel, aber es wird kein Webhook gesendet und es gibt kein Beispiel-PDF. Nutzen Sie **Test senden** bei einem Webhook-Endpunkt in der Konsole, um Ihren Empfänger auszuprobieren.

## Der Ablauf

1. Laden Sie die Datei mit `POST /files` hoch und starten Sie die Prüfung mit `POST /analyses`. Tragen Sie Ihre Vorgangs-ID in `metadata` ein und senden Sie einen `Idempotency-Key`.
2. Antworten Sie Ihren Nutzern sofort. Dokumente können in eine Warteschlange kommen; halten Sie die Anfrage daher nicht offen. Der Webhook meldet Ihnen, wann die Prüfung abgeschlossen ist.
3. Empfangen Sie `analysis.completed` oder `analysis.failed`, prüfen Sie die Signatur und stellen Sie einen Job in die Warteschlange.
4. Rufen Sie im Job die Analyse ab, leiten Sie sie anhand ihrer Zusammenfassung weiter und speichern Sie das Ergebnis und den PDF-Bericht beim Vorgang.
5. Senden Sie Vorgänge, die von einer Person geprüft werden müssen, mit angehängtem PDF an eine Prüfwarteschlange.
6. Verifizieren Sie den Bericht später anhand der gespeicherten Datei.

## 1. Hochladen und Prüfung starten

Dateien bis 4 MB werden als Multipart hochgeladen. Für größere Dateien bis 50 MB verwenden Sie einen Upload über eine vorsignierte URL, wie unter [Dateien](https://fileverity.com/de/docs/files.md#presigned) beschrieben. Das Erstellen der Analyse liefert `202` mit `status: processing` (ein Testschlüssel liefert `200` mit einem fertigen Beispiel), und Ihre `metadata` wird mit der Analyse und in jedem zugehörigen Webhook zurückgegeben.

**Dokument einreichen: cURL**

```bash
FILE_ID=$(curl -s https://api.fileverity.com/v1/files \
  -H "Authorization: Bearer $FILEVERITY_API_KEY" \
  -H "Idempotency-Key: intake-A-10492-file" \
  -F file=@application.pdf | jq -r .id)

curl https://api.fileverity.com/v1/analyses \
  -H "Authorization: Bearer $FILEVERITY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: intake-A-10492-check" \
  -d "{\"model\": \"fvdoc-3\", \"file\": \"$FILE_ID\", \"metadata\": {\"case_id\": \"A-10492\"}}"
```

**Dokument einreichen: JavaScript**

```javascript
import { readFile } from "node:fs/promises";
import { basename } from "node:path";

const base = "https://api.fileverity.com/v1";
const auth = { Authorization: "Bearer " + process.env.FILEVERITY_API_KEY };

async function call(path, init) {
  const response = await fetch(base + path, init);
  const body = await response.json();
  if (!response.ok) throw new Error(body.error.code + " (" + body.error.request_id + ")");
  return body;
}

export async function submit(caseId, path) {
  const form = new FormData();
  form.append("file", new Blob([await readFile(path)]), basename(path));
  const file = await call("/files", {
    method: "POST",
    headers: { ...auth, "Idempotency-Key": `intake-${caseId}-file` },
    body: form,
  });
  return call("/analyses", {
    method: "POST",
    headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": `intake-${caseId}-check` },
    body: JSON.stringify({ model: "fvdoc-3", file: file.id, metadata: { case_id: caseId } }),
  });
}
```

**Dokument einreichen: Python**

```python
import os
import requests

BASE = "https://api.fileverity.com/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["FILEVERITY_API_KEY"]}

def call(method, path, **kwargs):
    headers = {**HEADERS, **kwargs.pop("headers", {})}
    response = requests.request(method, BASE + path, headers=headers, timeout=90, **kwargs)
    body = response.json()
    if not response.ok:
        raise RuntimeError(body["error"]["code"] + " (" + body["error"]["request_id"] + ")")
    return body

def submit(case_id, path):
    with open(path, "rb") as source:
        file = call("POST", "/files", headers={"Idempotency-Key": f"intake-{case_id}-file"}, files={"file": source})
    return call(
        "POST",
        "/analyses",
        headers={"Idempotency-Key": f"intake-{case_id}-check"},
        json={"model": "fvdoc-3", "file": file["id"], "metadata": {"case_id": case_id}},
    )
```

**202 Accepted**

```json
{
  "id": "an_8Hk2pQ4vN9sL1xR7cW3mY6zB",
  "object": "analysis",
  "created": 1793700120,
  "model": "fvdoc-3-2026-11-03",
  "livemode": true,
  "status": "processing",
  "file": {
    "name": "application.pdf",
    "bytes": 18874368,
    "format": "pdf"
  },
  "report_id": null,
  "report_url": null,
  "summary": null,
  "result": null,
  "error": null,
  "progress": {
    "stage": "queued",
    "done": 0,
    "total": 0,
    "position": 1
  },
  "usage": {
    "amount": 599,
    "currency": "usd",
    "funding": "balance"
  },
  "metadata": {
    "case_id": "A-10492"
  },
  "completed_at": null,
  "expires_at": null
}
```

Speichern Sie `id` beim Vorgang. Da die Idempotenzschlüssel aus der Vorgangs-ID abgeleitet werden, liefert ein erneuter Aufruf von `submit` nach einem Timeout die ersten Antworten zurück, statt eine zweite Datei hochzuladen oder eine zweite Prüfung zu berechnen. Idempotenzschlüssel werden 24 Stunden lang aufbewahrt; siehe [Idempotenz](https://fileverity.com/de/docs/idempotency.md).

## 2. Das Ergebnis empfangen

Prüfen Sie die Signatur mit der Funktion `verify` aus [Webhooks](https://fileverity.com/de/docs/webhooks.md#signature), gespeichert als `verify.js` oder `verify.py`. Antworten Sie schnell mit `2xx` und erledigen Sie die Arbeit in einem Job. `enqueue` steht für Ihre eigene Job-Warteschlange. Speichern Sie jeden Job unter der Ereignis-ID, damit eine doppelte Zustellung keinen zweiten Job erzeugt.

**Webhook-Empfänger: JavaScript**

```javascript
import express from "express";
import { verify } from "./verify.js";

const app = express();

app.post("/webhooks/fileverity", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = verify(process.env.FILEVERITY_WEBHOOK_SECRET, req.headers, req.body.toString("utf8"));
  } catch {
    return res.sendStatus(400);
  }
  if (event.type === "analysis.completed" || event.type === "analysis.failed") {
    await enqueue({ eventId: event.id, analysisId: event.data.id });
  }
  res.sendStatus(200);
});
```

**Webhook-Empfänger: Python**

```python
import os

from fastapi import FastAPI, HTTPException, Request

from verify import verify

app = FastAPI()

@app.post("/webhooks/fileverity")
async def receive(request: Request):
    body = await request.body()
    try:
        event = verify(os.environ["FILEVERITY_WEBHOOK_SECRET"], request.headers, body)
    except (KeyError, ValueError):
        raise HTTPException(status_code=400)
    if event["type"] in ("analysis.completed", "analysis.failed"):
        enqueue(event["id"], event["data"]["id"])
    return {"ok": True}
```

## 3. Nach Risikostufe weiterleiten

`summary` ist die stabile, maschinenlesbare Einstufung einer Dokumentprüfung. `level` reicht von `L1` bis `L5`, und `action` ist der empfohlene nächste Schritt. Leiten Sie anhand beider Felder weiter und behandeln Sie jeden Wert, den Sie nicht kennen, als Vorgang für eine Person.

| Zusammenfassung | Bedeutung | Beispiel für die Weiterleitung |
| --- | --- | --- |
| `L1` oder `L2`, `accept` | Es wurden keine Anzeichen von Manipulation gefunden. `L1` bedeutet, dass die Quelle verifiziert wurde. | Automatisch fortfahren. Berichts-ID speichern. |
| `L1` oder `L2`, `note` | Keine Anzeichen von Manipulation, aber im Bericht ist von KI verfasster Text oder eine KI-Kennzeichnung markiert. | Fortfahren und den Vorgang markieren, wenn Ihre Richtlinie von KI verfasste Inhalte einschränkt. |
| `L3`, `review` | Erfordert manuelle Prüfung. | Reguläre Prüfwarteschlange. |
| `L4`, `review` | Hohes Risiko. | Vorrangige Prüfwarteschlange. |
| `L5`, `reject` | Im Bericht sind Probleme nachgewiesen. | Vorrangige Prüfwarteschlange. Eine Person bestätigt, bevor etwas abgelehnt wird. |
| `status` ist `failed` oder `canceled` | Die Prüfung wurde nicht abgeschlossen, und Ihnen wurde nichts berechnet. | Das gespeicherte Original erneut hochladen oder den Vorgang an eine Person übergeben. |

Die Zuordnung folgt Ihrer eigenen Richtlinie. Ändern Sie die Funktion, um `L3` in die vorrangige Warteschlange zu legen oder `note` zur Prüfung weiterzuleiten. Zu den möglichen Aktionen gehört außerdem `resubmit`, das die Funktion als Anforderung einer weiteren Kopie der Datei behandelt. Jedes Feld der Zusammenfassung ist unter [Analysen](https://fileverity.com/de/docs/analyses.md#summary-document) beschrieben.

**Eine abgeschlossene Prüfung weiterleiten: JavaScript**

```javascript
export function route(analysis) {
  if (analysis.status !== "succeeded") return "manual";
  const { action, level } = analysis.summary;
  if (action === "accept") return "accept";
  if (action === "note") return "accept_with_note";
  if (action === "resubmit") return "request_new_copy";
  if (action === "reject" || level === "L4" || level === "L5") return "priority_review";
  return "review";
}
```

**Eine abgeschlossene Prüfung weiterleiten: Python**

```python
def route(analysis):
    if analysis["status"] != "succeeded":
        return "manual"
    action = analysis["summary"]["action"]
    level = analysis["summary"]["level"]
    if action == "accept":
        return "accept"
    if action == "note":
        return "accept_with_note"
    if action == "resubmit":
        return "request_new_copy"
    if action == "reject" or level in ("L4", "L5"):
        return "priority_review"
    return "review"
```

## 4. Berichts-ID und PDF speichern

Der Job ruft die Analyse ab, leitet sie weiter und speichert, was Sie später brauchen. Er verwendet `call`, `route` und die Zugangsdaten aus den vorherigen Schritten erneut, und `saveCase` und `saveReport` stehen für Ihren eigenen Speicher. Laden Sie das PDF im selben Job herunter: Das Analyseergebnis wird 7 Tage lang aufbewahrt, die Seitenbilder und Textauszüge eines Dokuments 3 Stunden lang, und Fileverity bewahrt danach nur den Verifizierungsdatensatz auf, nicht den vollständigen Bericht.

**Eine abgeschlossene Prüfung verarbeiten: JavaScript**

```javascript
export async function handleResult(analysisId) {
  const analysis = await call(`/analyses/${analysisId}`, { headers: auth });
  const decision = route(analysis);
  await saveCase(analysis.metadata.case_id, {
    analysisId: analysis.id,
    reportId: analysis.report_id,
    reportUrl: analysis.report_url,
    summary: analysis.summary,
    model: analysis.model,
    decision,
  });
  if (analysis.status === "succeeded") {
    const pdf = await fetch(`${base}/analyses/${analysis.id}/report.pdf`, { headers: auth });
    await saveReport(analysis.report_id, Buffer.from(await pdf.arrayBuffer()));
  }
  return decision;
}
```

**Eine abgeschlossene Prüfung verarbeiten: Python**

```python
def handle_result(analysis_id):
    analysis = call("GET", f"/analyses/{analysis_id}")
    decision = route(analysis)
    save_case(analysis["metadata"]["case_id"], {
        "analysis_id": analysis["id"],
        "report_id": analysis["report_id"],
        "report_url": analysis["report_url"],
        "summary": analysis["summary"],
        "model": analysis["model"],
        "decision": decision,
    })
    if analysis["status"] == "succeeded":
        pdf = requests.get(f"{BASE}/analyses/{analysis['id']}/report.pdf", headers=HEADERS, timeout=90)
        pdf.raise_for_status()
        save_report(analysis["report_id"], pdf.content)
    return decision
```

- `report_id` (`FV-XXXX-XXXX-XXXX`) ist der Schlüssel zum Audit-Nachweis. Speichern Sie sie beim Vorgang, in Ihrer Datenbank und auf jedem Entscheidungsschreiben.
- `model` ist der datierte Snapshot, der das Ergebnis erzeugt hat, sodass Sie später genau sagen können, welche Version das Dokument geprüft hat.
- Bewahren Sie die Originaldatei in Ihrem eigenen Speicher auf. Fileverity löscht eine hochgeladene Datei, sobald die Analyse beginnt, und nach einer fehlgeschlagenen Prüfung ist ein neuer Upload nötig. Siehe [Datenaufbewahrung und Sicherheit](https://fileverity.com/de/docs/security.md).

## 5. Die Warteschlange für die Prüfung durch Menschen betreiben

Legen Sie pro weitergeleitetem Vorgang eine Aufgabe an. Geben Sie der prüfenden Person alles, was sie für eine Entscheidung braucht, ohne die API erneut aufzurufen:

- Die Vorgangs-ID, `level`, `action`, `reason` und die Befund-IDs in `findings`, mit angehängtem PDF-Bericht. Das PDF listet jeden Befund mit Fundstelle und Beleg auf.
- Die Originaldatei, die Sie gespeichert haben.
- `report_url`, die öffentliche Verifizierungsseite. Sie bestätigt den Berichtsdatensatz und den Fingerabdruck der Datei, ersetzt aber nicht die Lektüre des Berichts.

Halten Sie die prüfende Person, die Entscheidung und den Zeitpunkt in Ihrem eigenen System neben `report_id` fest.

> **Hinweis.** Eine unauffällige Prüfung ist kein Beweis dafür, dass ein Dokument echt ist, und ein Befund ist kein Beweis für Betrug. Lehnen Sie einen Vorgang nicht allein auf Grundlage des API-Ergebnisses ab. Bei Einkommens-, Identitäts- oder Eigentumsnachweisen bestätigen Sie die Angaben beim Aussteller über einen Kontakt, den Sie selbst recherchiert haben. Der [Ratgeber zum Erkennen gefälschter und KI-generierter Dokumente](https://fileverity.com/de/ratgeber/gefaelschte-und-ki-generierte-dokumente-erkennen) zeigt, was je Dokumenttyp zu prüfen ist.

## 6. Den Bericht später verifizieren

Monate später können Auditoren oder Kunden den Datensatz ohne Fileverity-Konto prüfen. Öffnen Sie die gespeicherte `report_url`. Sie zeigt die Berichtsart, den festgehaltenen Befund und den nächsten Schritt, den SHA-256-Fingerabdruck der untersuchten Datei und den RFC-3161-Zeitstempel. Ziehen Sie Ihr gespeichertes Original auf die Seite, dann berechnet Ihr Browser dessen SHA-256-Hash und vergleicht ihn mit dem Datensatz; es wird nichts hochgeladen. Jede erneut gespeicherte, konvertierte oder bearbeitete Kopie ergibt einen anderen Fingerabdruck.

Denselben Vergleich können Sie in Ihrem eigenen System durchführen:

**Fingerabdruck des gespeicherten Originals berechnen: cURL**

```bash
sha256sum application.pdf
```

**Fingerabdruck des gespeicherten Originals berechnen: JavaScript**

```javascript
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";

const digest = createHash("sha256").update(await readFile("application.pdf")).digest("hex");
console.log(digest);
```

**Fingerabdruck des gespeicherten Originals berechnen: Python**

```python
import hashlib

print(hashlib.sha256(open("application.pdf", "rb").read()).hexdigest())
```

Die Verifizierung analysiert die Datei nicht erneut und bescheinigt nicht, dass ein Dokument authentisch ist. Sie hält fest, welche Datei untersucht wurde, was der Bericht ergab und wann. Der vollständige Bericht existiert nur in dem PDF, das Sie gespeichert haben. Die Felder des Berichts sind im Abschnitt [Das Berichtsobjekt](https://fileverity.com/de/docs/analyses.md#report) beschrieben.

## Bevor Sie live gehen

- Entwickeln Sie zunächst mit einem Testschlüssel. Er liefert sofort ein Beispiel, Stufe `L2` mit Maßnahme `accept`, ohne das Modell auszuführen. Webhooks und der PDF-Download erfordern einen Live-Schlüssel, den Sie mit Ihrem kostenlosen Start ausprobieren können. Übergeben Sie die anderen Zusammenfassungen aus [Analysen](https://fileverity.com/de/docs/analyses.md#summary-document) an `route`, um jeden Zweig zu testen.
- Behandeln Sie `503 service_unavailable`: Warten Sie die Sekunden aus `retry-after` ab und wiederholen Sie die Anfrage mit demselben `Idempotency-Key`. Siehe [Wenn der Dienst ausgelastet ist](https://fileverity.com/de/docs/analyses.md#busy).
- Legen Sie für den Schlüssel ein monatliches Ausgabenlimit fest und richten Sie einen Webhook für `balance.low` ein, damit der Eingangs-Workflow nicht mit `insufficient_balance` stoppt. Siehe [Preise und Abrechnung](https://fileverity.com/de/docs/pricing.md#alerts).
- Rufen Sie die API von Ihrem Server aus auf und bewahren Sie Schlüssel in einem Secrets-Manager auf, nie in Browser-Code.
- Geben Sie den Wert des Headers `x-request-id` an, wenn Sie den [Support kontaktieren](https://fileverity.com/de/kontakt).

