# API France Vérification v1 — documentation complète

> Adresse de l'API : https://france-verification.com/api/v1  
> Manuel en ligne : https://france-verification.com/developpeurs  
> Ce fichier est généré depuis la même source que le manuel : il est toujours à jour.


---

## Démarrage rapide

L'API de France Vérification analyse un document (RIB, fiche de paie, pièce d'identité, avis d'imposition, justificatif de domicile...) et rend un **verdict d'authenticité** : `CONFORME`, `SUSPECT`, `NON-CONFORME` ou `FAKE`, avec un score de confiance et une analyse détaillée.

Elle utilise exactement le même moteur que le site : analyse par intelligence artificielle, contrôles forensiques du fichier (métadonnées, logiciel de retouche, structure du PDF, signatures numériques) et validations françaises (SIRET, IBAN, numéro de sécurité sociale).

### En trois appels

1. **Envoyer** un ou plusieurs fichiers : `POST /submissions`. La réponse arrive tout de suite, avec un identifiant d'envoi (`sub_...`) et une vérification (`chk_...`) par document.
2. **Attendre** : une analyse prend environ 10 secondes. Vous recevez un webhook `submission.completed`, ou vous relisez l'envoi.
3. **Lire** le résultat : `GET /submissions/{id}`.

### Adresse de l'API

```
https://france-verification.com/api/v1
```

Toutes les requêtes passent en HTTPS et portent votre clé dans l'en-tête `Authorization`.

### 1. Créer une clé

Connectez-vous, puis ouvrez la page **API** de votre espace : [france-verification.com/mon-api](https://france-verification.com/mon-api). Générez une **clé test** (`fv_test_...`) pour développer sans rien payer, puis une **clé live** (`fv_live_...`) pour la production.

### 2. Envoyer un document

```bash
curl -X POST https://france-verification.com/api/v1/submissions \
  -H "Authorization: Bearer fv_live_VOTRE_CLE" \
  -F "files[]=@rib.pdf"
```

Réponse (`202 Accepted`) avec une clé live. Avec une clé test, la réponse arrive déjà terminée, avec un résultat fictif (voir [Mode test](https://france-verification.com/developpeurs/mode-test)) :

```json
{
  "id": "sub_1842",
  "mode": "live",
  "status": "processing",
  "final_check": null,
  "documents_count": 1,
  "credits_charged": 1,
  "checks": [
    {
      "id": "chk_52817",
      "submission_id": "sub_1842",
      "type": "document",
      "status": "pending",
      "verdict": null,
      "confidence_score": null,
      "document_type": null,
      "document_label": null,
      "filename": "rib.pdf",
      "page": null,
      "archive_name": null,
      "refunded": false,
      "created_at": "2026-09-28T14:49:06Z",
      "completed_at": null,
      "report_url": null
    }
  ],
  "created_at": "2026-09-28T14:49:06Z",
  "completed_at": null
}
```

### 3. Lire le résultat

```bash
curl https://france-verification.com/api/v1/submissions/sub_1842 \
  -H "Authorization: Bearer fv_live_VOTRE_CLE"
```

Quand `status` vaut `completed`, chaque vérification porte son `verdict`, son `confidence_score` et son `document_type`. Ajoutez `?detail=full` pour obtenir l'analyse complète.

### Même chose en PHP, Python et Node.js

```php
<?php
$ch = curl_init('https://france-verification.com/api/v1/submissions');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('FV_API_KEY')],
    CURLOPT_POSTFIELDS => ['files[0]' => new CURLFile('rib.pdf')],
    CURLOPT_RETURNTRANSFER => true,
]);
$submission = json_decode(curl_exec($ch), true);
echo $submission['id'];
```

```python
import os, requests

response = requests.post(
    "https://france-verification.com/api/v1/submissions",
    headers={"Authorization": f"Bearer {os.environ['FV_API_KEY']}"},
    files=[("files[]", open("rib.pdf", "rb"))],
)
submission = response.json()
print(submission["id"])
```

```javascript
// Node.js 18 ou plus
import { readFile } from "node:fs/promises";

const form = new FormData();
form.append("files[]", new Blob([await readFile("rib.pdf")]), "rib.pdf");

const response = await fetch("https://france-verification.com/api/v1/submissions", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.FV_API_KEY}` },
  body: form,
});
const submission = await response.json();
console.log(submission.id);
```

### Et ensuite

- [Authentification](https://france-verification.com/developpeurs/authentification) : clés live et test.
- [Envoyer des documents](https://france-verification.com/developpeurs/envoyer-des-documents) : formats, archives, JSON base64.
- [Webhooks](https://france-verification.com/developpeurs/webhooks) : être prévenu sans interroger l'API.
- [Objets](https://france-verification.com/developpeurs/objets) : la liste complète des champs.


---

## Authentification

Chaque requête porte une clé API dans l'en-tête `Authorization`, au format Bearer :

```
Authorization: Bearer fv_live_4f9c...
```

### Deux clés par compte

| Clé | Préfixe | Ce qu'elle fait |
|---|---|---|
| Live | `fv_live_` | Vraies analyses. Débite les crédits du compte, exactement comme le site. |
| Test | `fv_test_` | Réponses fictives et immédiates. Aucun crédit, aucune analyse, aucun fichier conservé. Voir [Mode test](https://france-verification.com/developpeurs/mode-test). |

Les clés se créent, se régénèrent et se révoquent sur la page **API** de votre espace : [france-verification.com/mon-api](https://france-verification.com/mon-api).

- Une clé n'est affichée **qu'une seule fois**, au moment de sa création. Nous n'en gardons qu'une empreinte : une clé perdue se régénère, elle ne se relit pas.
- Régénérer une clé révoque immédiatement la précédente du même type.
- Les deux clés donnent accès au même compte, mais chacune ne voit que ses propres données : la clé test ne lit que les envois de test, la clé live ne lit jamais les envois de test.

### Bonnes pratiques

- Gardez la clé côté serveur, dans une variable d'environnement (`FV_API_KEY`). Ne l'intégrez jamais dans une application mobile, un site web ou un dépôt Git.
- Si une clé a fuité, régénérez-la : l'ancienne est refusée dans la seconde.

### Clé absente ou invalide

```http
HTTP/1.1 401 Unauthorized
```

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Clé API absente, invalide ou révoquée. En-tête attendu : Authorization: Bearer fv_live_... (ou fv_test_...)."
  }
}
```

Après 30 échecs d'authentification en une minute depuis la même adresse IP, les requêtes suivantes reçoivent `429 rate_limited` pendant une minute.


---

## Envoyer des documents

```
POST /api/v1/submissions
```

Un **envoi** (`submission`) regroupe un ou plusieurs fichiers déposés ensemble. Chaque document qu'il contient devient une **vérification** (`check`), analysée séparément.

### Ce qui compte comme un document

| Fichier envoyé | Vérifications créées |
|---|---|
| Une image (JPG, PNG, HEIC...) | 1 |
| Un PDF d'une page | 1 |
| Un PDF de N pages | N : une par page, car un dossier scanné contient souvent plusieurs pièces différentes |
| Une archive ZIP ou RAR | une par fichier qu'elle contient (et une par page de chaque PDF) |

Chaque vérification coûte le même nombre de crédits que sur le site (valeur exacte : `credits_per_document` dans [GET /account](https://france-verification.com/developpeurs/compte-et-referentiels)). Les crédits sont débités à l'envoi, puis **rendus automatiquement** si le fichier n'est pas un document ou si l'analyse échoue (`refunded: true`).

### Paramètres

| Paramètre | Type | Obligatoire | Description |
|---|---|---|---|
| `files[]` | fichier(s) | oui | Envoi en `multipart/form-data`. Pour un seul fichier, `file` est aussi accepté. |
| `files` | tableau JSON | oui (en JSON) | Envoi en `application/json` : `[{"filename": "rib.pdf", "content_base64": "JVBERi0..."}]`. |
| `final_check` | texte | non | Demande une vérification croisée de tout le dossier. Voir [Dossiers](https://france-verification.com/developpeurs/dossiers). |

En-têtes facultatifs :

| En-tête | Description |
|---|---|
| `Idempotency-Key` | Une valeur unique de votre choix (1 à 255 caractères). Si la même valeur est renvoyée dans les 24 heures, l'API rend l'envoi déjà créé, sans rien débiter une deuxième fois (réponse `200`, en-tête `Idempotent-Replayed: true`). À utiliser pour rejouer une requête sans risque après une coupure réseau. |

Paramètre de réponse facultatif : `?detail=full` renvoie aussi le détail des analyses (voir [Objets](https://france-verification.com/developpeurs/objets)).

### Limites

Les limites exactes de votre compte sont renvoyées par [GET /account](https://france-verification.com/developpeurs/compte-et-referentiels), dans `limits` :

- `max_file_size_mb` : taille maximale d'un fichier ;
- `max_files_per_submission` : nombre maximal de fichiers dans un envoi ;
- `allowed_extensions` : formats acceptés.

Une archive peut contenir au plus 300 fichiers et 1 Go une fois décompressée. Les fichiers d'un format non accepté sont ignorés à l'intérieur d'une archive (ils ne sont pas facturés).

### Exemple : plusieurs fichiers en multipart

```bash
curl -X POST https://france-verification.com/api/v1/submissions \
  -H "Authorization: Bearer $FV_API_KEY" \
  -H "Idempotency-Key: dossier-client-8841" \
  -F "files[]=@piece-identite.jpg" \
  -F "files[]=@bulletins-de-salaire.pdf"
```

### Exemple : JSON et base64 (Zapier, Make, n8n)

Les outils sans code gèrent souvent mal l'envoi de fichiers. Envoyez alors le contenu encodé en base64 :

```bash
curl -X POST https://france-verification.com/api/v1/submissions \
  -H "Authorization: Bearer $FV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      { "filename": "rib.pdf", "content_base64": "JVBERi0xLjQKJ..." }
    ]
  }'
```

### Réponse

`202 Accepted` et l'objet [Submission](https://france-verification.com/developpeurs/objets). À ce stade, les vérifications sont en `pending` : lisez la suite dans [Suivre un envoi](https://france-verification.com/developpeurs/suivre-un-envoi).

### Erreurs possibles

| Statut | Code | Cause |
|---|---|---|
| 402 | `insufficient_credits` | Solde insuffisant. `details` donne `credits_available`, `credits_required` et `documents_count`. Rien n'est débité. |
| 422 | `validation_failed` | Aucun fichier, format refusé, fichier trop lourd, trop de fichiers, `final_check` inconnu. `details.fields` dit quel champ pose problème. |
| 413 | `validation_failed` | Requête trop volumineuse. |


---

## Suivre un envoi

```
GET /api/v1/submissions/{id}
```

Une analyse prend environ 10 secondes par document ; un gros envoi peut prendre quelques minutes. Deux façons de savoir quand c'est fini :

1. **Le webhook (recommandé)** : nous appelons votre serveur avec l'évènement `submission.completed` dès que tout est terminé. Voir [Webhooks](https://france-verification.com/developpeurs/webhooks).
2. **L'interrogation** : relisez l'envoi jusqu'à ce que `status` vaille `completed`. Espacez les appels d'au moins 5 secondes.

### Exemple

```bash
curl "https://france-verification.com/api/v1/submissions/sub_1842?detail=full" \
  -H "Authorization: Bearer $FV_API_KEY"
```

```json
{
  "id": "sub_1842",
  "mode": "live",
  "status": "completed",
  "final_check": null,
  "documents_count": 1,
  "credits_charged": 1,
  "checks": [
    {
      "id": "chk_52817",
      "submission_id": "sub_1842",
      "type": "document",
      "status": "completed",
      "verdict": "SUSPECT",
      "confidence_score": 0.55,
      "document_type": "rib",
      "document_label": "Relevé d'identité bancaire",
      "filename": "rib.pdf",
      "page": null,
      "archive_name": null,
      "refunded": false,
      "created_at": "2026-09-28T14:49:06Z",
      "completed_at": "2026-09-28T14:49:17Z",
      "report_url": "https://france-verification.com/api/v1/checks/chk_52817/report.pdf",
      "details": {
        "analysis": "Le document présente la structure d'un RIB...",
        "positive_points": "Clé RIB valide, IBAN au bon format",
        "negative_points": "Police de l'IBAN différente du reste du document",
        "anomalies": "Métadonnées : fichier modifié après sa création",
        "recommendations": "Demander un RIB téléchargé directement depuis l'espace bancaire",
        "technical_quality": "bon"
      }
    }
  ],
  "created_at": "2026-09-28T14:49:06Z",
  "completed_at": "2026-09-28T14:49:17Z"
}
```

### Quand un envoi est-il terminé ?

`status` passe à `completed` quand **chaque** vérification est `completed` ou `failed` et que la vérification de dossier éventuelle est close. Une vérification `failed` ne rend pas de verdict et son crédit est rendu (`refunded: true`) : vous pouvez renvoyer le fichier.

### Lire le verdict

| `verdict` | Signification | Action conseillée |
|---|---|---|
| `CONFORME` | Aucune anomalie détectée. | Accepter. |
| `SUSPECT` | Des éléments demandent une vérification humaine. | Relire `details`, demander une autre pièce. |
| `NON-CONFORME` | Le document ne respecte pas les règles attendues pour son type (mentions manquantes, incohérences), ou ce n'est pas un document. | Refuser ou demander une autre pièce. |
| `FAKE` | Falsification détectée (logiciel de retouche, montage, métadonnées). | Refuser. |

`confidence_score` (de 0 à 1) mesure la confiance dans l'authenticité : plus il est haut, plus le document est jugé authentique. Il peut être `null` quand le document n'a pas pu être évalué (par exemple un fichier qui n'est pas un document).

Votre code doit décider sur `verdict`, jamais sur le texte de `details`, qui est rédigé pour un humain et peut changer de formulation.

### Erreurs possibles

| Statut | Code | Cause |
|---|---|---|
| 404 | `not_found` | Identifiant inconnu, appartenant à un autre compte, ou envoi de test lu avec une clé live (et inversement). |


---

## Vérifications

Une **vérification** (`check`, identifiant `chk_...`) est l'analyse d'un document. Ces routes les lisent une par une, les listent, donnent leur rapport PDF et les suppriment.

### Lire une vérification

```
GET /api/v1/checks/{id}
```

| Paramètre | Description |
|---|---|
| `detail` | `minimal` (par défaut) ou `full` pour ajouter l'objet `details`. |

```bash
curl "https://france-verification.com/api/v1/checks/chk_52817?detail=full" \
  -H "Authorization: Bearer $FV_API_KEY"
```

Réponse : l'objet [Check](https://france-verification.com/developpeurs/objets).

### Lister les vérifications

```
GET /api/v1/checks
```

Avec une clé live, la liste contient **toutes** les vérifications du compte, y compris celles faites sur le site. Avec une clé test, seulement les vérifications fictives des envois de test. Tri : de la plus récente à la plus ancienne.

| Paramètre | Description |
|---|---|
| `page` | Numéro de page, à partir de 1. |
| `per_page` | De 1 à 100. Par défaut 50. |
| `detail` | `minimal` ou `full`. |

```json
{
  "data": [ { "id": "chk_52817", "status": "completed", "verdict": "SUSPECT", "...": "..." } ],
  "pagination": { "page": 1, "per_page": 50, "total": 128, "has_more": true }
}
```

Tant que `has_more` vaut `true`, demandez la page suivante.

### Télécharger le rapport PDF

```
GET /api/v1/checks/{id}/report.pdf
```

Le même rapport que sur le site, prêt à archiver ou à transmettre. Disponible quand la vérification est `completed` (le champ `report_url` de l'objet donne l'adresse exacte).

```bash
curl -o rapport.pdf https://france-verification.com/api/v1/checks/chk_52817/report.pdf \
  -H "Authorization: Bearer $FV_API_KEY"
```

| Statut | Code | Cause |
|---|---|---|
| 404 | `not_found` | Vérification inconnue, ou pas encore terminée. |
| 409 | `not_available_in_test_mode` | Les vérifications de test n'ont pas de rapport. |

### Supprimer une vérification

```
DELETE /api/v1/checks/{id}
```

Efface tout de suite le fichier envoyé, son aperçu, l'analyse et le journal technique. Réponse : `204 No Content`. La suppression est définitive.

Sans suppression de votre part, les fichiers envoyés sont effacés automatiquement au bout de 7 jours ; le résultat de l'analyse, lui, reste consultable.


---

## Dossiers

Vérifier chaque pièce ne suffit pas toujours : un faux dossier de location peut être fait de pièces toutes plausibles une à une, mais incohérentes entre elles (nom, adresse, employeur, montants, dates). La **vérification de dossier** recoupe toutes les pièces d'un même envoi et rend un verdict global.

### Demander une vérification de dossier

Ajoutez `final_check` à l'envoi, avec **toutes les pièces du dossier dans le même envoi** :

```bash
curl -X POST https://france-verification.com/api/v1/submissions \
  -H "Authorization: Bearer $FV_API_KEY" \
  -F "final_check=dossier_location" \
  -F "files[]=@cni-recto-verso.pdf" \
  -F "files[]=@bulletins.pdf" \
  -F "files[]=@avis-imposition.pdf" \
  -F "files[]=@rib.pdf"
```

### Instructions disponibles

La liste à jour, avec le coût en crédits de chacune, est renvoyée par [GET /document-types](https://france-verification.com/developpeurs/compte-et-referentiels) (tableau `final_checks`).

| `final_check` | Ce qui est recoupé |
|---|---|
| `dossier_location` | Identité, adresses, employeur, revenus, dates, complétude du dossier. |
| `capacite_financiere` | Titulaire, salaires réellement crédités, régularité, cohérence avec l'avis d'imposition, incidents. |
| `dossier_embauche` | Identité, droit au travail, titulaire du RIB, numéro de sécurité sociale, diplômes, périodes. |
| `identite` | Même personne sur toutes les pièces, recto et verso, validité, bande MRZ, droit au séjour. |
| `dossier_copropriete` | Convocation, quorum et tantièmes, mandats, majorités, appels de fonds. |

### Le résultat

L'envoi porte un objet `final_check` :

```json
"final_check": {
  "type": "dossier_location",
  "status": "completed",
  "result": {
    "id": "chk_52830",
    "type": "final_check",
    "status": "completed",
    "verdict": "SUSPECT",
    "confidence_score": 0.48,
    "document_type": "dossier_location",
    "document_label": "Vérifier un dossier de location",
    "...": "..."
  }
}
```

| `final_check.status` | Signification |
|---|---|
| `pending` | Les pièces sont encore en cours d'analyse. |
| `processing` | Le recoupement est en cours. |
| `completed` | `result` porte le verdict du dossier. |
| `failed` | Le recoupement a échoué ; les verdicts de chaque pièce restent valables. |
| `skipped` | Aucune pièce exploitable : le recoupement n'a pas eu lieu et son crédit est rendu. |

Deux règles de cohérence : le verdict du dossier n'est **jamais meilleur** que celui de sa pire pièce, et son score reste cohérent avec son verdict.

L'envoi ne passe à `completed` (et le webhook ne part) qu'une fois le recoupement terminé.


---

## Webhooks

Plutôt que d'interroger l'API en boucle, donnez-nous une adresse : nous l'appelons dès qu'un envoi est terminé.

### Configurer

Sur la page **API** de votre espace ([france-verification.com/mon-api](https://france-verification.com/mon-api)) :

1. saisissez l'adresse de votre serveur (obligatoirement en `https://`, et accessible depuis Internet) ;
2. copiez le **secret de signature** (`whsec_...`) dans votre configuration ;
3. cliquez sur « Envoyer un webhook de test » : vous recevez un évènement `ping`.

Une seule adresse par compte. Les envois de la clé test déclenchent aussi le webhook, ce qui permet de tout tester sans crédit.

### Ce que nous envoyons

Une requête `POST`, corps JSON :

```json
{
  "id": "evt_9120",
  "type": "submission.completed",
  "created_at": "2026-09-28T14:49:17Z",
  "data": {
    "id": "sub_1842",
    "mode": "live",
    "status": "completed",
    "...": "l'objet Submission complet, au format minimal"
  }
}
```

| Évènement | Quand |
|---|---|
| `submission.completed` | Toutes les vérifications de l'envoi sont terminées (et le dossier éventuel aussi). `data` = l'objet [Submission](https://france-verification.com/developpeurs/objets). |
| `ping` | Webhook de test, déclenché depuis la page API. |

En-têtes :

| En-tête | Contenu |
|---|---|
| `X-FV-Event` | Le type de l'évènement. |
| `X-FV-Delivery` | L'identifiant de l'évènement (`evt_...`), identique à `id` dans le corps. |
| `X-FV-Signature` | `t=<horodatage unix>,v1=<signature>` |
| `User-Agent` | `FranceVerification-Webhooks/1.0` |

### Répondre

Répondez un code `2xx` en moins de 10 secondes. Faites le traitement lourd après avoir répondu (file d'attente de votre côté).

Sans réponse `2xx`, nous réessayons 5 fois : après 1 minute, 5 minutes, 30 minutes, 2 heures, puis 6 heures. Ensuite l'évènement est abandonné, mais le résultat reste lisible par `GET /submissions/{id}`. Le même évènement peut donc arriver plus d'une fois : dédoublonnez sur `id`.

### Vérifier la signature

La signature prouve que l'appel vient de nous. Calculez un HMAC SHA-256 de `<t>.<corps brut>` avec votre secret, et comparez-le à `v1`. Refusez aussi un horodatage vieux de plus de 5 minutes.

```php
<?php
$secret = getenv('FV_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_FV_SIGNATURE'] ?? ''), $sig);

$expected = hash_hmac('sha256', $sig['t'] . '.' . $body, $secret);
if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) $sig['t']) > 300) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
http_response_code(200);
```

```python
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(item.split("=", 1) for item in header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", "")) and abs(time.time() - int(parts["t"])) <= 300
```

```javascript
import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ""));
}
```

Important : signez le corps **brut**, tel que reçu, avant tout décodage JSON.

### Changer de secret

« Régénérer le secret » sur la page API crée un nouveau secret : l'ancien ne signe plus rien dès cet instant. Mettez votre configuration à jour aussitôt.


---

## Compte et référentiels

Deux routes de lecture, pour que votre code ne contienne aucune valeur écrite en dur.

### Le compte

```
GET /api/v1/account
```

```json
{
  "email": "contact@votre-societe.fr",
  "name": "Votre société",
  "mode": "live",
  "credits": 482,
  "credits_per_document": 1,
  "limits": {
    "max_file_size_mb": 100,
    "max_files_per_submission": 100,
    "allowed_extensions": ["zip", "rar", "pdf", "jpeg", "jpg", "png", "heic"],
    "requests_per_minute": 60
  }
}
```

| Champ | Description |
|---|---|
| `mode` | `live` ou `test`, selon la clé utilisée. |
| `credits` | Solde disponible. `null` avec une clé test. |
| `credits_per_document` | Crédits débités par document (par page de PDF). |
| `limits` | Limites d'envoi en vigueur. Lisez-les plutôt que de les recopier : elles peuvent être relevées. |

Les crédits s'achètent sur le site : [france-verification.com/plans](https://france-verification.com/plans).

### Types de documents, instructions de dossier, verdicts

```
GET /api/v1/document-types
```

```json
{
  "document_types": [
    { "key": "fiche_de_paie", "label": "Fiche de paie" },
    { "key": "rib", "label": "RIB / IBAN" },
    { "key": "carte_identite", "label": "Carte nationale d'identité" },
    { "key": "autre_document", "label": "Autre document" }
  ],
  "final_checks": [
    { "type": "dossier_location", "label": "Vérifier un dossier de location", "description": "...", "credits": 1 }
  ],
  "verdicts": ["CONFORME", "SUSPECT", "NON-CONFORME", "FAKE"]
}
```

- `document_types` : toutes les valeurs possibles du champ `document_type` d'une vérification. Un document d'un type non listé reste analysé, avec `document_type = "autre_document"`.
- `final_checks` : les valeurs acceptées par le paramètre `final_check` d'un envoi.
- `verdicts` : les quatre valeurs possibles de `verdict`.

De nouveaux types de documents et de nouvelles instructions peuvent apparaître : prévoyez qu'une valeur inconnue soit traitée comme `autre_document`.


---

## Erreurs et limites

### Format des erreurs

Toutes les erreurs ont la même forme :

```json
{
  "error": {
    "code": "insufficient_credits",
    "message": "Crédits insuffisants pour cet envoi.",
    "details": { "credits_available": 0, "credits_required": 2, "documents_count": 2 }
  }
}
```

Votre code doit réagir sur `code`. `message` est une phrase en français pour un humain : sa formulation peut changer. `details` n'est présent que lorsqu'il apporte une information.

### Codes

| Statut HTTP | `code` | Signification |
|---|---|---|
| 401 | `unauthorized` | Clé absente, invalide ou révoquée. |
| 402 | `insufficient_credits` | Solde insuffisant pour l'envoi. Rien n'a été débité. |
| 404 | `not_found` | Ressource ou route inconnue, ou qui appartient à un autre compte. |
| 405 | `method_not_allowed` | Méthode HTTP non prévue sur cette route. |
| 409 | `not_available_in_test_mode` | Fonction indisponible avec une clé test (rapport PDF). |
| 413 | `validation_failed` | Requête trop volumineuse. |
| 422 | `validation_failed` | Paramètres invalides. `details.fields` liste les champs en cause. |
| 429 | `rate_limited` | Trop de requêtes. Attendez le nombre de secondes indiqué par `Retry-After`. |
| 500 | `internal_error` | Erreur de notre côté. Réessayez ; si elle persiste, écrivez à contact@france-verification.com. |

La liste des codes peut s'allonger ; un code ne change jamais de sens.

### Limite de débit

60 requêtes par minute et par clé. Chaque réponse porte :

| En-tête | Contenu |
|---|---|
| `X-RateLimit-Limit` | 60 |
| `X-RateLimit-Remaining` | Requêtes restantes dans la minute. |
| `Retry-After` | En cas de `429`, secondes à attendre. |

Un envoi peut contenir jusqu'à `max_files_per_submission` fichiers : pour un gros volume, regroupez les fichiers plutôt que de multiplier les appels.

### Rejouer sans risque

Une requête interrompue (coupure réseau, délai dépassé) peut avoir été traitée ou non. Envoyez vos `POST /submissions` avec un en-tête `Idempotency-Key` : en cas de doute, renvoyez exactement la même requête avec la même clé, vous obtiendrez l'envoi déjà créé sans double débit. La clé reste réservée 24 heures.


---

## Mode test

Avec une clé `fv_test_...`, toute l'API fonctionne, mais sans analyse réelle ni crédit débité. C'est l'environnement pour écrire et tester votre intégration.

### Ce qui change

| | Clé live | Clé test |
|---|---|---|
| Analyse | Réelle (IA, forensique, contrôles) | Aucune : résultat fictif |
| Crédits | Débités | Jamais |
| Réponse de `POST /submissions` | `status: "processing"`, verdict quelques secondes plus tard | `status: "completed"` immédiatement |
| Découpage des PDF et archives | Une vérification par page ou par fichier | Une vérification par fichier envoyé |
| Webhook `submission.completed` | Oui | Oui |
| Rapport PDF | Oui | Non (`409 not_available_in_test_mode`) |
| Fichiers conservés | 7 jours au plus | Aucun |

Les formats, tailles et paramètres sont validés exactement comme en live : une requête acceptée en test l'est aussi en live.

### Choisir le verdict fictif

Le résultat dépend du **nom du fichier** envoyé :

| Le nom contient | `status` | `verdict` | `confidence_score` |
|---|---|---|---|
| `error` | `failed` | `null` | `null` |
| `non_conforme` ou `non-conforme` | `completed` | `NON-CONFORME` | 0.25 |
| `fake` | `completed` | `FAKE` | 0 |
| `suspect` | `completed` | `SUSPECT` | 0.55 |
| autre chose | `completed` | `CONFORME` | 0.92 |

Exemple : envoyez `test_fake.pdf` pour vérifier que votre code bloque bien un faux document.

Avec `final_check`, le dossier fictif rend toujours `CONFORME`.


---

## Objets

Référence complète des objets renvoyés par l'API. Les dates sont en UTC, au format ISO 8601 (`2026-09-28T14:49:06Z`). Un champ sans valeur vaut `null` : il est toujours présent.

### Submission (envoi)

| Champ | Type | Description |
|---|---|---|
| `id` | texte | Identifiant, préfixe `sub_`. |
| `mode` | texte | `live` ou `test`. |
| `status` | texte | `processing` puis `completed`. |
| `final_check` | objet ou `null` | Vérification de dossier demandée. Voir ci-dessous. |
| `documents_count` | entier | Nombre de vérifications créées. |
| `credits_charged` | entier | Crédits débités à l'envoi (les remboursements apparaissent sur chaque vérification, `refunded`). |
| `checks` | tableau de Check | Une vérification par document. |
| `created_at` | date | Création de l'envoi. |
| `completed_at` | date ou `null` | Fin de l'envoi. |

#### final_check

| Champ | Type | Description |
|---|---|---|
| `type` | texte | L'instruction demandée (`dossier_location`...). |
| `status` | texte | `pending`, `processing`, `completed`, `failed` ou `skipped`. |
| `result` | Check ou `null` | La synthèse du dossier (`type` = `final_check`), quand elle existe. |

### Check (vérification)

| Champ | Type | Description |
|---|---|---|
| `id` | texte | Identifiant, préfixe `chk_`. |
| `submission_id` | texte ou `null` | L'envoi d'origine. `null` pour une vérification faite sur le site ou pour une synthèse de dossier. |
| `type` | texte | `document` ou `final_check` (synthèse de dossier). |
| `status` | texte | `pending`, `processing`, `completed` ou `failed`. |
| `verdict` | texte ou `null` | `CONFORME`, `SUSPECT`, `NON-CONFORME` ou `FAKE`. Présent seulement quand `status` = `completed`. |
| `confidence_score` | nombre ou `null` | De 0 à 1 : confiance dans l'authenticité du document. |
| `document_type` | texte ou `null` | Type détecté, une clé de [GET /document-types](https://france-verification.com/developpeurs/compte-et-referentiels) (pour une synthèse : l'instruction de dossier). |
| `document_label` | texte ou `null` | Libellé lisible du type, parfois plus précis que le type (« RIB Crédit Agricole »). |
| `filename` | texte | Nom du fichier. Une page de PDF découpé s'appelle `nom_part2_sur_5.pdf`. |
| `page` | objet ou `null` | `{ "number": 2, "total": 5 }` pour une page de PDF découpé. |
| `archive_name` | texte ou `null` | L'archive ZIP ou RAR d'origine. |
| `refunded` | booléen | `true` si le crédit de ce document a été rendu (fichier qui n'est pas un document, ou analyse en échec). |
| `created_at` | date | Création. |
| `completed_at` | date ou `null` | Fin de l'analyse (`completed` ou `failed`). |
| `report_url` | texte ou `null` | Adresse du rapport PDF, quand `status` = `completed`. |
| `details` | objet | Seulement avec `?detail=full`. Voir ci-dessous. |

#### details (avec `?detail=full`)

Textes rédigés en français pour un humain. Affichez-les, mais ne programmez pas de décision dessus : utilisez `verdict`.

| Champ | Type | Description |
|---|---|---|
| `analysis` | texte ou `null` | L'analyse complète : ce qui a été contrôlé, et les résultats des contrôles techniques du fichier. |
| `positive_points` | texte ou `null` | Ce qui plaide pour l'authenticité. |
| `negative_points` | texte ou `null` | Ce qui plaide contre. |
| `anomalies` | texte ou `null` | Anomalies relevées. |
| `recommendations` | texte ou `null` | Ce que nous conseillons de faire. |
| `technical_quality` | texte ou `null` | Qualité du fichier : `excellent`, `bon`, `moyen` ou `faible`. |

### Event (webhook)

| Champ | Type | Description |
|---|---|---|
| `id` | texte | Identifiant, préfixe `evt_`. Servez-vous en pour dédoublonner. |
| `type` | texte | `submission.completed` ou `ping`. |
| `created_at` | date | Émission de l'évènement. |
| `data` | objet | Pour `submission.completed` : l'objet Submission. |

### Error

| Champ | Type | Description |
|---|---|---|
| `error.code` | texte | Code stable. Voir [Erreurs et limites](https://france-verification.com/developpeurs/erreurs-et-limites). |
| `error.message` | texte | Explication pour un humain. |
| `error.details` | objet | Facultatif. |


---

## Stabilité et versions

Votre intégration doit pouvoir tourner des années sans être retouchée. C'est la règle que nous nous imposons.

### Notre engagement sur la v1

Dans `/api/v1`, nous pouvons seulement **ajouter** :

- un nouveau champ dans une réponse ;
- une nouvelle valeur dans une liste de référence (`document_types`, `final_checks`) ;
- un nouveau code d'erreur ;
- un nouveau paramètre facultatif ;
- une nouvelle route ou un nouvel évènement de webhook.

Nous ne ferons **jamais** dans la v1 : renommer ou retirer un champ, changer son type, changer le sens d'une valeur, retirer une valeur de `verdict` ou de `status`, déplacer une route, rendre obligatoire un paramètre facultatif.

### Ce que votre code doit accepter

- Des champs qu'il ne connaît pas : ignorez-les.
- Une valeur de `document_type` qu'il ne connaît pas : traitez-la comme `autre_document`.
- Un code d'erreur qu'il ne connaît pas : traitez-le selon son statut HTTP.

### Si une rupture devient nécessaire

Elle se fera dans une **nouvelle version** (`/api/v2`). La v1 restera servie pendant la transition, et son arrêt sera annoncé par e-mail aux comptes qui l'utilisent.

### Journal des versions

| Date | Changement |
|---|---|
| 2026-09-28 | Première version publique : envois (fichiers, archives, JSON base64, dossiers), vérifications, rapport PDF, suppression, compte, référentiels, webhooks signés, clés live et test. |

