# Vérifications de documents dans un processus de réception

> Ajoutez des vérifications de documents à un processus de réception : téléversement, réception du webhook, aiguillage selon le niveau de risque, mise en file d’attente de l’examen humain, enregistrement de l’ID de rapport et vérification ultérieure.

Source : https://fileverity.com/fr/docs/intake-workflow

Cet exemple place des vérifications de documents en amont d’un processus qui reçoit des documents de l’extérieur : demandes de prêt, déclarations de sinistre, intégration de fournisseurs ou notes de frais. Chaque document est vérifié une fois, aiguillé selon son niveau de risque, et son ID de rapport est conservé comme trace d’audit. L’API renvoie un niveau et une action recommandée. Votre politique et les personnes chargées de l’examen prennent la décision.

Il utilise le modèle `fvdoc-3` avec les appels décrits dans [Fichiers](https://fileverity.com/fr/docs/files.md), [Analyses](https://fileverity.com/fr/docs/analyses.md) et [Webhooks](https://fileverity.com/fr/docs/webhooks.md). Avec une clé de test, le téléversement et la vérification renvoient immédiatement un exemple, mais aucun webhook n’est envoyé et il n’existe pas de PDF d’exemple. Utilisez **Envoyer un test** sur un point de terminaison webhook dans la Console pour essayer votre récepteur.

## Le déroulement

1. Téléversez le fichier avec `POST /files` et lancez la vérification avec `POST /analyses`. Placez l’ID de votre dossier dans `metadata` et envoyez une `Idempotency-Key`.
2. Répondez immédiatement à votre utilisateur. Les documents pouvant être mis en file d’attente, ne gardez pas sa requête ouverte : le webhook vous indique quand la vérification est terminée.
3. Recevez `analysis.completed` ou `analysis.failed`, vérifiez la signature et mettez une tâche en file d’attente.
4. Dans la tâche, récupérez l’analyse, aiguillez-la selon son résumé, puis enregistrez le résultat et le rapport PDF avec le dossier.
5. Envoyez vers une file d’examen les dossiers qui nécessitent l’intervention d’une personne, avec le PDF en pièce jointe.
6. Plus tard, vérifiez le rapport en le confrontant au fichier conservé.

## 1. Téléverser le fichier et lancer la vérification

Les fichiers de 4 Mo maximum sont téléversés en multipart. Pour les fichiers plus volumineux (jusqu’à 50 Mo), utilisez un téléversement par URL présignée, comme décrit dans [Fichiers](https://fileverity.com/fr/docs/files.md#presigned). La création de l’analyse renvoie `202` avec `status: processing` (une clé de test renvoie `200` avec un exemple terminé), et votre `metadata` est renvoyé avec l’analyse et dans chaque webhook qui lui est associé.

**Soumettre un document: 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\"}}"
```

**Soumettre un document: 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 } }),
  });
}
```

**Soumettre un document: 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
}
```

Enregistrez `id` avec le dossier. Comme les clés sont dérivées de l’ID du dossier, relancer `submit` après un délai d’attente dépassé renvoie les premières réponses au lieu de téléverser un second fichier ou de facturer une seconde vérification. Les clés sont conservées 24 heures ; voir [Idempotence](https://fileverity.com/fr/docs/idempotency.md).

## 2. Recevoir le résultat

Vérifiez la signature avec la fonction `verify` de [Webhooks](https://fileverity.com/fr/docs/webhooks.md#signature), enregistrée sous le nom `verify.js` ou `verify.py`. Répondez rapidement par `2xx` et effectuez le travail dans une tâche. `enqueue` représente votre propre file de tâches. Enregistrez chaque tâche sous l’ID de l’événement, afin qu’un envoi en double ne crée pas de seconde tâche.

**Récepteur de webhooks: 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);
});
```

**Récepteur de webhooks: 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. Aiguiller selon le niveau de risque

`summary` est le verdict stable et lisible par machine d’une vérification de document. `level` va de `L1` à `L5` et `action` est la prochaine étape recommandée. Aiguillez selon les deux, et traitez toute valeur que vous ne reconnaissez pas comme un cas à confier à une personne.

| Résumé | Signification | Exemple d’aiguillage |
| --- | --- | --- |
| `L1` ou `L2`, `accept` | Aucun signe de falsification n’a été trouvé. `L1` signifie que la source a été vérifiée. | Continuez automatiquement. Enregistrez l’ID de rapport. |
| `L1` ou `L2`, `note` | Aucun signe de falsification, mais un texte rédigé par IA ou une étiquette IA est signalé dans le rapport. | Continuez, et signalez le dossier si votre politique limite le contenu rédigé par IA. |
| `L3`, `review` | Nécessite un examen manuel. | File d’examen standard. |
| `L4`, `review` | Risque élevé. | File d’examen prioritaire. |
| `L5`, `reject` | Des problèmes sont établis dans le rapport. | File d’examen prioritaire. Une personne confirme avant tout refus. |
| `status` vaut `failed` ou `canceled` | La vérification ne s’est pas terminée et rien ne vous a été facturé. | Téléversez à nouveau l’original conservé, ou confiez le dossier à une personne. |

La correspondance dépend de votre politique. Pour envoyer `L3` dans la file prioritaire, ou faire passer `note` en examen, modifiez la fonction. L’ensemble des actions contient aussi `resubmit`, que la fonction traite comme une demande d’une autre copie du fichier. Chaque champ du résumé est décrit dans [Analyses](https://fileverity.com/fr/docs/analyses.md#summary-document).

**Aiguiller une vérification terminée: 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";
}
```

**Aiguiller une vérification terminée: 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. Enregistrer l’ID de rapport et le PDF

La tâche récupère l’analyse, l’aiguille et enregistre ce dont vous aurez besoin plus tard. Elle réutilise `call`, `route` et les identifiants d’accès des étapes précédentes, tandis que `saveCase` et `saveReport` représentent votre propre stockage. Téléchargez le PDF dans la même tâche : le résultat de l’analyse est conservé 7 jours, les images des pages et les extraits de texte d’un document 3 heures, et Fileverity ne conserve ensuite que l’enregistrement de vérification, pas le rapport complet.

**Traiter une vérification terminée: 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;
}
```

**Traiter une vérification terminée: 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`) est la clé d’accès à la trace d’audit. Enregistrez-le avec le dossier, dans votre base de données et sur tout courrier de décision.
- `model` est l’instantané daté qui a produit le résultat, ce qui vous permet d’indiquer plus tard précisément quelle version a vérifié le document.
- Conservez le fichier original dans votre propre stockage. Fileverity supprime un fichier téléversé au début de l’analyse, et une vérification échouée nécessite un nouveau téléversement. Voir [Conservation des données et sécurité](https://fileverity.com/fr/docs/security.md).

## 5. Gérer la file d’examen humain

Créez une tâche par dossier aiguillé. Donnez à la personne chargée de l’examen ce dont elle a besoin pour décider sans rappeler l’API :

- L’ID du dossier, `level`, `action`, `reason` et les ID des constats dans `findings`, avec le rapport PDF en pièce jointe. Le PDF présente chaque constat avec son emplacement et ses preuves.
- Le fichier original que vous avez conservé.
- `report_url`, la page de vérification publique. Elle confirme l’enregistrement du rapport et l’empreinte du fichier, mais ne remplace pas la lecture du rapport.

Consignez dans votre propre système la personne qui a effectué l’examen, la décision et l’heure, à côté de `report_id`.

> **Remarque.** Une vérification sans anomalie ne prouve pas qu’un document est authentique, et un constat ne prouve pas une fraude. Ne refusez pas un dossier sur le seul résultat de l’API. Pour les justificatifs de revenus, d’identité ou de propriété, confirmez auprès de l’émetteur par un contact que vous avez trouvé vous-même. Le [guide de détection de la fraude documentaire](https://fileverity.com/fr/guides/fraude-documentaire-ia-quels-controles) explique quoi vérifier selon le type de document.

## 6. Vérifier le rapport plus tard

Des mois plus tard, un auditeur ou un client peut vérifier l’enregistrement sans compte Fileverity. Ouvrez le `report_url` que vous avez conservé. Il affiche le type de rapport, le constat et la prochaine étape enregistrés, l’empreinte SHA-256 du fichier examiné et l’horodatage RFC 3161. Déposez votre original conservé sur la page : votre navigateur calcule son SHA-256 et le compare à l’enregistrement ; rien n’est téléversé. Toute copie réenregistrée, convertie ou modifiée donne une empreinte différente.

Vous pouvez effectuer la même comparaison dans votre propre système :

**Calculer l’empreinte de l’original conservé: cURL**

```bash
sha256sum application.pdf
```

**Calculer l’empreinte de l’original conservé: 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);
```

**Calculer l’empreinte de l’original conservé: Python**

```python
import hashlib

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

La vérification n’analyse pas à nouveau le fichier et ne certifie pas qu’un document est authentique. Elle consigne quel fichier a été examiné, ce que disait le rapport et à quel moment. Le rapport complet n’existe que dans le PDF que vous avez conservé. Les champs du rapport sont décrits dans [l’objet rapport](https://fileverity.com/fr/docs/analyses.md#report).

## Avant la mise en production

- Développez avec une clé de test. Elle renvoie immédiatement un exemple, de niveau `L2` avec l’action `accept`, sans exécuter le modèle. Les webhooks et le téléchargement du PDF nécessitent une clé Live, que vous pouvez essayer avec votre démarrage gratuit. Soumettez à `route` les autres résumés décrits dans [Analyses](https://fileverity.com/fr/docs/analyses.md#summary-document) pour tester chaque branche.
- Gérez `503 service_unavailable` : attendez le nombre de secondes indiqué par `retry-after`, puis réessayez avec la même `Idempotency-Key`. Voir [Lorsque le service est saturé](https://fileverity.com/fr/docs/analyses.md#busy).
- Définissez une limite de dépense mensuelle sur la clé et ajoutez un webhook `balance.low`, afin que la réception ne s’arrête pas avec `insufficient_balance`. Voir [Tarifs et facturation](https://fileverity.com/fr/docs/pricing.md#alerts).
- Appelez l’API depuis votre serveur et conservez les clés dans un gestionnaire de secrets, jamais dans du code exécuté dans un navigateur.
- Indiquez la valeur de l’en-tête `x-request-id` lorsque vous [contactez l’assistance](https://fileverity.com/fr/contact).

