Documentation API PVID Cloud
L'API PVID Cloud vous permet de,
- générer les liens PVID à transmettre à vos utilisateurs pour qu'ils puissent effectuer le parcours de vérification d'identité,
- récupérer les informations des PVID effectués par vos utilisateurs,
- configurer le parcours de vérification d'identité pour qu'il s'intègre au mieux dans votre parcours métier
- tester l'appareil mobile de votre utilisateur avant qu'il ne démarre son parcours
Cette documentation a pour but de vous guider dans l'intégration et la consommation de l'API PVID Cloud.
Ressources
Getting Started
Pour utiliser l'API PVID Cloud vous devez posséder des identifiants. Vous pouvez demander vos identifiants directement au support PVID Cloud.
1. Authentification
Pour l'ensemble des routes de l'API PVID Cloud il faut être en capacité de,
- soumettre une authentification valide
- déchiffrer le retour de l'API PVID Cloud
L'API PVID Cloud utilise OAuth 2.0 avec des tokens JWT. Toutes les requêtes API authentifiées nécessitent un token JWT valide 5 minutes.
1.1 Se connecter à l'API (POST /api/login)
Route permettant d'obtenir un token JWT pour pouvoir requêter les autres endpoints.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
username | string | Identifiant de votre application | ✅ |
password | string | Mot de passe associé | ✅ |
Réponse | Succès
| Code | Status |
| ——– | —– |
| 200 | OK |
Exemple de réponse
- snippet.json
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 300, "refresh_expires_in": 1800, "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "not-before-policy": 0, "session_state": "123e4567-e89b-12d3-a456-426614174000", "scope": "email profile" }
Réponse | Erreurs
| Code | Status | Valeur |
| — | — | — |
| 400 | Bad Request | Missing credentials |
| 401 | Unauthorized | Invalid credentials or Keycloak error |
1.2 Headers d'authentification
Pour vous authentifier, il faut ajouter ce header à toutes vos requêtes.
| Header | Valeur |
| — | — |
Authorization | Bearer ACCESS_TOKEN |
1.3 Rafraîchir le token (POST /api/refresh_token)
Route pour obtenir un nouveau token d'accès sans vous reconnecter.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
refresh_token | string | Token de rafraîchissement obtenu lors du login | ✅ |
Réponse | Succès
| Code | Status |
| — | — |
| 200 | OK |
Exemple de réponse
- snippet.json
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 300, "refresh_expires_in": 1800, "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "not-before-policy": 0, "session_state": "123e4567-e89b-12d3-a456-426614174000", "scope": "email profile" }
Réponse | Erreurs
| Code | Status | Message |
| — | — | — |
| 400 | Bad Request | Missing refresh token |
| 400 | Bad Request | Invalid refresh token |
| 401 | Unauthorized | Invalid credentials or Keycloak error |
1.4 Infos complémentaires
- Le token d'accès est valide 300 secondes (5 minutes).
- Le refresh token est valide 1800 secondes (30 minutes).
- Utilisez le token via le header :
Authorization: Bearer eyJhbGciOiJSUzI1NiIs... - Si vous utilisez un token expiré, vous recevrez une erreur
401 Unauthorized
2. Signature et déchiffrement des requêtes
Pour des raisons de sécurité, vos requêtes vers PVID Cloud doivent être signées et les réponses de notre API devront être déchiffrées.
2.1 Mécanisme de chiffrement
Pour toutes les routes sécurisées (sauf /api/login et /api/refresh_token), un header signature est obligatoire. Ce header permet de valider l'authenticité de la requête.
Header requis
| Header | Type | Description | Requis |
| — | — | — | — |
signature | string | Signature générée à partir du paramètre date | ✅ |
Algorithme de génération de la signature
| Élément | Valeur |
| — | — |
| Algorithme | AES-256-CBC avec padding PKCS7 |
| Clé | 32 premiers caractères de SHA256(tokenSecret) en hexadécimal |
| IV | 16 premiers caractères de SHA256(SHA256(tokenSecret)) en hexadécimal |
| Valeur à chiffrer | Le paramètre date (format YYYY-MM-DD HH:mm:ss) |
| Sortie | Base64 |
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
2.1.1 Génération de la signature en PHP
- snippet.php
<?php function generateSignature(string $date, string $tokenSecret): string { $key = substr(hash('sha256', $tokenSecret), 0, 32); $iv = substr(hash('sha256', hash('sha256', $tokenSecret)), 0, 16); return openssl_encrypt($date, 'aes-256-cbc', $key, 0, $iv); } $date = "2026-05-20 14:30:00"; $tokenSecret = "votre-token-secret"; $signature = generateSignature($date, $tokenSecret); echo $signature;
2.1.2 Génération de la signature en Python
- snippet.python
import hashlib from Crypto.Cipher import AES import base64 def generate_signature(date: str, token_secret: str) -> str: key = hashlib.sha256(token_secret.encode()).hexdigest()[:32] iv = hashlib.sha256(hashlib.sha256(token_secret.encode()).hexdigest().encode()).hexdigest()[:16] cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode()) pad_len = 16 - (len(date) % 16) padded_date = date + chr(pad_len) * pad_len encrypted = cipher.encrypt(padded_date.encode()) return base64.b64encode(encrypted).decode() date = "2026-05-20 14:30:00" token_secret = "votre-token-secret" signature = generate_signature(date, token_secret) print(signature)
2.1.3 Génération de la signature en JavaScript
- snippet.javascript
const crypto = require('crypto'); function generateSignature(date, tokenSecret) { const key = crypto.createHash('sha256').update(tokenSecret).digest('hex').slice(0, 32); const fullHash = crypto.createHash('sha256').update(tokenSecret).digest('hex'); const iv = crypto.createHash('sha256').update(fullHash).digest('hex').slice(0, 16); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); let encrypted = cipher.update(date, 'utf8', 'base64'); encrypted += cipher.final('base64'); return encrypted; } const date = "2026-05-20 14:30:00"; const tokenSecret = "votre-token-secret"; const signature = generateSignature(date, tokenSecret); console.log(signature);
2.1.4 Génération de la signature en Java
- snippet.java
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Base64; public class SignatureGenerator { private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02x", b)); return sb.toString(); } public static String generateSignature(String date, String tokenSecret) throws Exception { MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); String fullHash = bytesToHex(sha256.digest(tokenSecret.getBytes(StandardCharsets.UTF_8))); String key = fullHash.substring(0, 32); sha256.reset(); String iv = bytesToHex(sha256.digest(fullHash.getBytes(StandardCharsets.UTF_8))).substring(0, 16); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"), new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8))); byte[] encrypted = cipher.doFinal(date.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encrypted); } }
2.2 Déchiffrement des réponses
En dehors des routes d'authentification (/api/login et /api/refresh_token), toutes les réponses de l'API sont chiffrées. Il faut donc les déchiffrer pour pouvoir lire la réponse.
Le paramètre date est obligatoire.
Algorithme de chiffrement des réponses
| Élément | Valeur |
| — | — |
| Algorithme | AES-256-CBC |
| Clé | SHA256(date + tokenSecret) |
| IV | 16 premiers octets du SHA256 de la clé |
| Encodage Base64 |
2.2.1 Déchiffrement des réponses en PHP
- snippet.php
<?php function decryptResponse(string $encryptedData, string $date, string $tokenSecret): array { // Décodage Base64 $encrypted = base64_decode($encryptedData); // Génération de la clé (SHA256 de date + tokenSecret) $key = hash('sha256', $date . $tokenSecret, true); // Génération de l'IV (16 premiers octets du SHA256 de la clé) $iv = substr(hash('sha256', $key, true), 0, 16); // Déchiffrement AES-256-CBC $decrypted = openssl_decrypt($encrypted, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); // Conversion JSON en tableau return json_decode($decrypted, true); } $encryptedResponse = "U2FsdGVkX1+abc123..."; $date = "2026-05-20 14:30:00"; $tokenSecret = "votre-token-secret"; $data = decryptResponse($encryptedResponse, $date, $tokenSecret); print_r($data);
2.2.2 Déchiffrement en Python
- snippet.python
import base64 import hashlib import json from Crypto.Cipher import AES def decrypt_response(encrypted_data: str, date: str, token_secret: str) -> dict: # Décodage Base64 encrypted = base64.b64decode(encrypted_data) # Génération de la clé key = hashlib.sha256((date + token_secret).encode()).digest() # Génération de l'IV iv = hashlib.sha256(key).digest()[:16] # Déchiffrement AES-256-CBC cipher = AES.new(key, AES.MODE_CBC, iv) decrypted = cipher.decrypt(encrypted) # Suppression du padding PKCS7 padding_len = decrypted[-1] decrypted = decrypted[:-padding_len] return json.loads(decrypted.decode('utf-8')) # Exemple d'utilisation # pip install pycryptodome encrypted_response = "U2FsdGVkX1+abc123..." # Réponse chiffrée de l'API date = "2026-05-20 14:30:00" # Date envoyée dans la requête token_secret = "votre-token-secret" # Votre clé secrète API data = decrypt_response(encrypted_response, date, token_secret) print(data)
2.2.3 Déchiffrement en JavaScript
- snippet.javascript
const crypto = require('crypto'); function decryptResponse(encryptedData, date, tokenSecret) { // Décodage Base64 const encrypted = Buffer.from(encryptedData, 'base64'); // Génération de la clé const key = crypto.createHash('sha256').update(date + tokenSecret).digest(); // Génération de l'IV const iv = crypto.createHash('sha256').update(key).digest().slice(0, 16); // Déchiffrement AES-256-CBC const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); let decrypted = decipher.update(encrypted); decrypted = Buffer.concat([decrypted, decipher.final()]); return JSON.parse(decrypted.toString('utf-8')); } // Exemple d'utilisation const encryptedResponse = "U2FsdGVkX1+abc123..."; // Réponse chiffrée de l'API const date = "2026-05-20 14:30:00"; // Date envoyée dans la requête const tokenSecret = "votre-token-secret"; // Votre clé secrète API const data = decryptResponse(encryptedResponse, date, tokenSecret); console.log(data);
2.2.4 Déchiffrement en Java
- snippet.java
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Arrays; import java.util.Base64; public class ApiDecrypter { public static String decryptResponse(String encryptedData, String date, String tokenSecret) throws Exception { // Décodage Base64 byte[] encrypted = Base64.getDecoder().decode(encryptedData); // Génération de la clé (SHA256 de date + tokenSecret) MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); byte[] key = sha256.digest((date + tokenSecret).getBytes(StandardCharsets.UTF_8)); // Génération de l'IV (16 premiers octets du SHA256 de la clé) byte[] iv = Arrays.copyOf(sha256.digest(key), 16); // Déchiffrement AES-256-CBC Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); byte[] decrypted = cipher.doFinal(encrypted); return new String(decrypted, StandardCharsets.UTF_8); } } // Exemple d'utilisation public static void main(String[] args) throws Exception { String encryptedResponse = "U2FsdGVkX1+abc123..."; // Réponse chiffrée de l'API String date = "2026-05-20 14:30:00"; // Date envoyée dans la requête String tokenSecret = "votre-token-secret"; // Votre clé secrète API String json = decryptResponse(encryptedResponse, date, tokenSecret); System.out.println(json); }
3. Endpoints API
3.1 Vérification d'identité PVID
Cette section concerne les 2 méthodes permettant de générer un lien PVID et de récupérer les résultats d'une vérification d'identité après verdict.
POST /api/pvid | Générer un lien PVID
Générer un lien PVID à transmettre à un utilisateur pour qu'il puisse effectuer le parcours de vérification d'identité à distance.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
date | string | Date pour chiffrement | ✅ |
customerEmail | string | Email de l'utilisateur à vérifier | ✅ |
urlAfterSuccess | string | URL de redirection après succès | ✅ |
urlAfterFailOrAbort | string | URL de redirection en cas d'échec/abandon | ✅ |
isPriority | integer | Traitement Express (0 ou 1). Surcoût applicable | ❌ |
linkValiditySharing | integer | Durée de validité en minutes (max 60) | ❌ |
disableLinkSharing | integer | Désactive le partage du lien via SMS/Qr-code (0 ou 1) | ❌ |
addUserAlertScreen | integer | Afficher un écran d'alerte à l'utilisateur (0 ou 1) | ❌ |
addUserAlertText | string | Ajouter un texte personnalisé sur l'écran d'alerte (5-120 caractères) | ❌ |
acceptedDocuments | array | Documents acceptés (ex: ["FR-passport"]) | ❌ |
webhook | string | URL de webhook pour cette vérification (surcharge du webhook global, HTTPS obligatoire) | ❌ |
Notes
- Le paramètre
webhookest optionnel : si non fourni, le webhook global de configuration sera utilisé - Conservez l'
idde la vérification pour les requêtes ultérieures (récupération des résultats) - L'URL de la vérification expire après le délai spécifié (par défaut 60 minutes, max 60 minutes) ; passé ce délai il faut demander un nouveau lien PVID Cloud
Réponse | Succès
| Code | Status |
| — | — |
| 201 | Created |
Exemple de réponse
- snippet.json
{ "id": 987654, "isPriority": 0, "customerEmail": "jean.dupont@example.com", "date": "2026-05-20 14:25:00", "dateExpireLink": "2026-05-20 14:55:00", "url": "https://pvid-front.../verify/abc123xyz789", "urlAfterSuccess": "https://votre-site.com/verification/succes", "urlAfterFailOrAbort": "https://votre-site.com/verification/echec", "fullHashSha256": "a1b2c3d4e5f6..." }
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 400 | Bad Request | N/A | Request payload contains invalid "form" data. |
| 400 | Bad Request | no_email | No email parameter found in your request |
| 400 | Bad Request | email_not_valid | This value is not a valid email address. |
| 400 | Bad Request | empty_url_after_success | No urlAfterSuccess found in your request |
| 400 | Bad Request | empty_url_after_fail_or_abort | No urlAfterFailOrAbort found in your request |
| 400 | Bad Request | alert_text_too_short | This value is too short. It should have 5 characters or more. |
| 400 | Bad Request | alert_text_too_long | This value is too long. It should have 120 characters or less. |
| 422 | Unprocessable Entity | webhook | The webhook URL must use HTTPS |
| 422 | Unprocessable Entity | linkValiditySharing | This value should be of type int. |
| 422 | Unprocessable Entity | linkValiditySharing | This value should be positive. |
| 422 | Unprocessable Entity | disableLinkSharing | This value should be of type int. |
| 422 | Unprocessable Entity | disableLinkSharing | Values accepted : 0, 1. |
| 422 | Unprocessable Entity | addUserAlertScreen | This value should be of type int. |
| 422 | Unprocessable Entity | addUserAlertScreen | Values accepted : 0, 1. |
| 422 | Unprocessable Entity | acceptedDocuments | This value is not valid. |
| 422 | Unprocessable Entity | isPriority | This value should be of type int. |
| 422 | Unprocessable Entity | isPriority | Values accepted : 0, 1. |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
GET /api/pvid/{id} | Récupérer les résultats d'une vérification PVID
Récupérer les informations d'un ID PVID donné.
Réponse | Vérification validée
| Code | Status |
| — | — |
| 200 | OK |
Exemple de réponse
- snippet.json
{ "id": 987654, "dateAdded": "2026-05-20 14:25:00", "verdictDate": "2026-05-20 15:30:00", "status": "done", "verdict": "validated", "document": { "type": "ID_CARD", "emitCountry": "FR", "documentNumber": "AB1234567", "documentNumberLast4": "4567", "emitDate": "2020-01-15", "expirationDate": "2030-01-15", "holderFirstname": "Jean", "holderLastname": "DUPONT", "birthdate": "1985-06-20", "holderNationality": "FR", "holderGender": "M", "holderBirthplace": "Paris", "mrz": "IDFRADUPONT<<JEAN..." }, "files": { "availableUntil": "2026-06-20 15:30:00", "front": "https://storage.../front.jpg", "back": "https://storage.../back.jpg", "biometry": "https://storage.../bio.jpg" } }
Réponse | Vérification refusée
| Code | Status |
| — | — |
| 200 | OK |
Exemple de réponse
- snippet.json
{ "id": 987654, "dateAdded": "2026-05-20 14:25:00", "verdictDate": "2026-05-20 15:30:00", "status": "done", "verdict": "refused", "abortedReason": null, "refusalReasons": [ { "slug": "doc_is_expired", "text": "Les documents que vous nous avez présentés sont expirés. Il faut impérativement présenter une pièce d'identité en cours de validité" } ] }
Voir la liste complète des motifs d'abandon
Réponse | Vérification expirée
| Code | Status |
| — | — |
| 200 | OK |
Exemple de réponse
- snippet.json
{ "id": 987654, "dateAdded": "2026-05-20 14:25:00", "verdictDate": "2026-05-20 15:25:00", "status": "expired", "verdict": null, "abortedReason": "expired_link", "refusalReasons": null }
Réponse | Vérification abandonnée
| Code | Status |
| — | — |
| 200 | OK |
Exemple de réponse
- snippet.json
{ "id": 987654, "dateAdded": "2026-05-20 14:25:00", "verdictDate": "2026-05-20 14:35:00", "status": "aborted", "verdict": null, "abortedReason": "canceled", "refusalReasons": null }
Voir la liste complète des motifs d'abandon
Réponse | Vérification en cours
| Code | Status |
| — | — |
| 200 | OK |
Exemple de réponse
- snippet.json
{ "id": 987654, "dateAdded": "2026-05-20 14:25:00", "verdictDate": null, "status": "currently_created", "verdict": null, "abortedReason": null, "refusalReasons": null }
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
3.2 Configuration du parcours PVID
Cette section concerne les méthodes et les endpoints permettant de personnaliser votre parcours PVID.
La personnalisation du parcours concerne les éléments suivants,
- graphique : logo, couleurs des textes/titres/boutons, favicon
- technique : url de webhook
- règlementaire : liste des titres acceptés
- anti-fraude renforcé : écran d'alerte utilisateur, empêcher le partage du lien PVID
GET /api/configs | Obtenir la configuration PVID
Récupérer les paramètres de la personnalisation actuelle.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
date | string | Date pour le chiffrement (YYYY-MM-DD HH:mm:ss) | ✅ |
Réponse | Succès
| Code | Status |
| — | — |
| 200 | OK |
Exemple de réponse
- snippet.json
{ "webhook": "https://api.votre-domaine.com/pvid/webhook", "linkValiditySharing": 60, "disableLinkSharing": 0, "addUserAlertScreen": 1, "addUserAlertText": "<b>Attention</b>", "titleColor": "#003366", "textColor": "#333333", "linkColorText": "#0066CC", "buttonColorText": "#FFFFFF", "buttonColorBackground": "#003366", "ghostButtonColor": "#003366", "acceptedDocuments": [...], "logo": "data:image/png;base64,...", "favicon": "data:image/x-icon;base64,..." }
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
PUT /api/configs | Remplacer la configuration PVID
Méthode permettant de remplacer entièrement la configuration actuelle de votre PVID.
Attention : cette méthode remplacera l'ensemble des données de configuration en place. Pour effectuer une modification partielle de la configuration utiliser la méthode “PATCH” de mise à jour de la configuration.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
webhook | string | URL webhook (HTTPS obligatoire) | ❌ |
titleColor | string | Couleur titres (#XXXXXX) | ❌ |
textColor | string | Couleur textes (#XXXXXX) | ❌ |
linkColorText | string | Couleur liens (#XXXXXX) | ❌ |
buttonColorText | string | Couleur texte boutons (#XXXXXX) | ❌ |
buttonColorBackground | string | Couleur fond boutons (#XXXXXX) | ❌ |
ghostButtonColor | string | Couleur boutons fantômes (#XXXXXX) | ❌ |
linkValiditySharing | integer | Validité du lien en minutes (max: 60) | ❌ |
disableLinkSharing | integer | Désactiver partage (0 ou 1) | ❌ |
addUserAlertScreen | integer | Écran d'alerte fraude (0 ou 1) | ❌ |
addUserAlertText | string | Texte alerte (5-120 car.) | ❌ |
acceptedDocuments | array | Documents acceptés | ❌ |
date | string | Date chiffrement | ✅ |
Réponse | Succès
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 400 | Bad Request | alert_text_too_short | The alert's text you submitted is too short (less than 5 characters) |
| 400 | Bad Request | alert_text_too_long | The alert's text you submitted is too long (more than 120 characters) |
| 422 | Unprocessable Entity | champ en erreur parmi titleColor, textColor, linkColorText, buttonColorText, buttonColorBackground, ghostButtonColor | This value is not a valid CSS color |
| 422 | Unprocessable Entity | webhook | The webhook URL must use HTTPS |
| 422 | Unprocessable Entity | linkValiditySharing | This value should be of type int |
| 422 | Unprocessable Entity | linkValiditySharing | This value should be positive |
| 422 | Unprocessable Entity | disableLinkSharing | This value should be of type int |
| 422 | Unprocessable Entity | disableLinkSharing | Values accepted : 0, 1 |
| 422 | Unprocessable Entity | addUserAlertScreen | This value should be of type int |
| 422 | Unprocessable Entity | addUserAlertScreen | Values accepted : 0, 1 |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
PATCH /api/configs | Mettre à jour la configuration PVID
Méthode permettant de mettre à jour partiellement votre configuration PVID (seuls les champs fournis sont modifiés).
Paramètres
| Champ | Type | Description | Requis |
| — | — | — | — |
webhook | string | URL webhook (HTTPS obligatoire) | ❌ |
titleColor | string | Couleur titres (#XXXXXX) | ❌ |
textColor | string | Couleur textes (#XXXXXX) | ❌ |
linkColorText | string | Couleur liens (#XXXXXX) | ❌ |
buttonColorText | string | Couleur texte boutons (#XXXXXX) | ❌ |
buttonColorBackground | string | Couleur fond boutons (#XXXXXX) | ❌ |
ghostButtonColor | string | Couleur boutons fantômes (#XXXXXX) | ❌ |
linkValiditySharing | integer | Validité du lien en minutes (max: 60) | ❌ |
disableLinkSharing | integer | Désactiver partage (0 ou 1) | ❌ |
addUserAlertScreen | integer | Écran d'alerte fraude (0 ou 1) | ❌ |
addUserAlertText | string | Texte alerte (5-120 car.) | ❌ |
acceptedDocuments | array | Documents acceptés | ❌ |
date | string | Date chiffrement | ✅ |
Réponse | Succès
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 400 | Bad Request | alert_text_too_short | The alert's text you submitted is too short (less than 5 characters) |
| 400 | Bad Request | alert_text_too_long | The alert's text you submitted is too long (more than 120 characters) |
| 422 | Unprocessable Entity | champ en erreur parmi titleColor, textColor, linkColorText, buttonColorText, buttonColorBackground, ghostButtonColor | This value is not a valid CSS color |
| 422 | Unprocessable Entity | webhook | The webhook URL must use HTTPS |
| 422 | Unprocessable Entity | linkValiditySharing | This value should be of type int |
| 422 | Unprocessable Entity | linkValiditySharing | This value should be positive |
| 422 | Unprocessable Entity | disableLinkSharing | This value should be of type int |
| 422 | Unprocessable Entity | disableLinkSharing | Values accepted : 0, 1 |
| 422 | Unprocessable Entity | addUserAlertScreen | This value should be of type int |
| 422 | Unprocessable Entity | addUserAlertScreen | Values accepted : 0, 1 |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
DELETE /api/configs | Réinitialiser la configuration
Méthode permettant de réinitialiser la configuration de votre PVID pour revenir au paramètres par défaut.
Attention : cette requête supprime tous vos paramètres personnalisés
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
date | string | Date pour le chiffrement | ✅ |
Réponse | Succès
| Code | Status |
| — | — |
| 204 | No Content |
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
GET /api/configs/documents | Liste des documents d'identité acceptés
Endpoint permettant de récupérer la liste des documents d'identité acceptés par PVID.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
date | string | Date pour le chiffrement | ✅ |
Réponse | Succès
| Code | Status |
| — | — |
| 200 | OK |
Exemple de réponse
- snippet.json
{ "allowedDocuments": [ { "code": "FR", "name": "France", "documents": ["passport", "id_card", "residence_permit"] }, { "code": "BE", "name": "Belgium", "documents": ["passport", "id_card"] } ] }
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
POST /api/configs/files | Mettre à jour les fichiers
Méthode permettant de mettre à jour le logo et le favicon du parcours PVID.
Cette requête utilise le multipart/form-data pour l'upload des fichiers.
Paramètres
| Champ | Type | Description | Requis |
| — | — | — | — |
logo | file | Image PNG/JPEG (hauteur max: 64px) | ❌ |
favicon | file | Image PNG/JPEG/ICO (hauteur max: 64px) | ❌ |
date | string | Date pour chiffrement (YYYY-MM-DD HH:mm:ss) | ✅ |
Réponse | Succès
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 422 | Unprocessable Entity | logo.path | This value should be of type string |
| 422 | Unprocessable Entity | favicon.path | This value should be of type string |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
DELETE /api/configs/files | Supprimer les fichiers
Méthode permettant de supprimer vos logo/favicon du parcours PVID pour remettre les logo/favicon par défaut.
Attention : cette requête supprime votre logo et votre favicon.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
date | string | Date pour le chiffrement (YYYY-MM-DD HH:mm:ss) | ✅ |
Réponse | Succès
| Code | Status |
| — | — |
| 204 | No Content |
Réponse | Erreurs
| Code | Status | Slug | Message |
| ——– | —– | —– | ——– |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
3.3 Tests de compatibilité de l'appareil de l'utilisateur
Cette section concerne les endpoints permettant de tester, en amont du parcours PVID, certains paramètres de compatibilité de l'appareil de l'utilisateur.
GET /api/browser/list | Navigateurs compatibles
Méthode permettant de récupérer la liste des navigateurs, et leurs versions, pris en charge par le PVID.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
date | string | Date pour le chiffrement (YYYY-MM-DD HH:mm:ss) | ✅ |
Réponse | Succès
| Code | Status |
| — | — |
| 201 | OK |
Exemple de réponse
- snippet.json
{ "safari": "14", "chrome": "110", "firefox": "66", "samsung": "6", "opera": "73", "edge": "79", "miui": "13.21" }
GET /api/browser/check | Vérifier un navigateur
Méthode permettant de tester la compatibilité du navigateur de l'utilisateur.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
name | string | Nom du navigateur | ✅ |
version | string | Version du navigateur | ✅ |
Réponse | Succès
| Code | Status |
| — | — |
| 201 | OK |
Exemple de réponse
- snippet.json
{ "result": "The user's browser can be used for a PVID" }
Réponse | Erreurs
| Code | Slug | Message |
| — | — | — |
| 400 | missing_browser_name | The browser's name is missing |
| 400 | missing_browser_version | The browser's version is missing |
| 400 | unknown_browser_name | The browser's name is unknown |
| 400 | invalid_browser_version | The browser's version is invalid |
| 400 | empty_date | No date parameter found in your request |
| 400 | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | expired_date | Given datetime is older than current datetime |
| 400 | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 401 | error | JWT Token not found |
GET /api/camera/resolution | Résolution de la caméra
Méthode permettant de récupérer la résolution minimale requise pour la caméra de l'appareil de l'utilisateur.
Paramètres
| Paramètre | Type | Description | Requis |
| — | — | — | — |
date | string | Date pour le chiffrement (YYYY-MM-DD HH:mm:ss) | ✅ |
Réponse | Succès
Réponse | Erreurs
| Code | Status | Slug | Message |
| — | — | — | — |
| 400 | Bad Request | invalid_date | Wrong date format (must be YYYY-MM-DD HH:mm:ss, eg: 2021-10-19 20:10:06) |
| 400 | Bad Request | empty_date | No date parameter found in your request |
| 400 | Bad Request | expired_date | Given datetime is older than current datetime |
| 400 | Bad Request | date_in_future | Given datetime must be set between call submission and +10 minutes |
| 400 | Bad Request | validation_error | Signature verification failed |
| 400 | Bad Request | failed_signature | Signature verification failed |
| 401 | Unauthorized | N/A | JWT Token not found |
| 401 | Unauthorized | N/A | Expired JWT Token |
| 401 | Unauthorized | user_not_exist | Unknown user |
| 500 | Bad Request | internal_server_error | Internal server error |
4. Webhooks
Les événements webhook permettent au métier d'être informé en temps réel de chaque changement d'état d'un PVID.
4.1 Configuration
Pour recevoir des événements webhook il faut renseigner une URL en HTTPS dans le paramètre webhook des endpoints POST /api/pvid, PUT /api/configs ou PATCH /api/configs.
4.2 Réponse
Lors de la réception de l'événement vous devez nous retourner un code HTTP 200.
Si le code HTTP de réponse de votre part est différent, nous allons effectuer jusqu'à 48 tentatives. Nous augmenterons les timeout au fur et à mesure de ces tentatives selon le fonctionnement suivant,
- 1ère à 9ème tentative : 2.5s timeout
- 10ème à 19ème tentative : 4.5s timeout
- 20ème à 29ème tentative : 6.5s timeout
- 30ème à 39ème tentative : 8.5s timeout
- 40ème à 48ème tentative : 11s timeout
4.3 Evénements
Les événements transmis par PVID sont les suivants,
| Événement | Description | Déclencheur |
| — | — | — |
validated | PVID validé | L'identité a été vérifiée avec succès |
refused | PVID refusé | L'identité n'a pas pu être vérifiée |
aborted | PVID abandonné | L'utilisateur a abandonné le parcours |
expired | PVID expiré | Le lien de vérification a expiré |
4.4 Payload
Les informations des événements webhook sont transmises au format Json.
Exemple de payload
- snippet.json
{ "pvid_id": "987654", "event": "validated", "event_date": "2026-05-20T15:30:00+00:00" }
| Champ | Type | Description |
| — | — | — |
pvid_id | string | Identifiant unique de la vérification PVID |
event | string | Type d'événement (validated, refused, aborted, expired) |
event_date | string | Date et heure de l'événement (format ISO 8601) |
5. Annexes
5.1 Diagramme de séquence
Voici un diagramme de séquence simplifié de l'utilisation de PVID Cloud.
5.2 Documents d'identité
Format
La nomenclature des différents documents d'identité accepté par le PVID Docaposte est la suivante : {CODE_PAYS}-{TYPE_DOCUMENT}.
Ce format est à utiliser dans les endpoints permettant de personnaliser les titres acceptés par le PVID.
Il sera également utilisé dans l'ensemble des retours de l'API PVID Cloud.
Exemples
FR-passport— Passeport françaisFR-id_card— Carte d'identité françaiseBE-residence_permit— Titre de séjour belge
La liste complète des documents acceptés par pays est disponible via l'endpoint GET /api/configs/documents.
5.3 Motifs d'abandon
Ajout d'un paramètre dans l'URL d'abandon
Lorsque l'utilisateur est redirigé vers l'URL url_after_fail_or_abort transmis dans la requête de génération d'un lien PVID PVID Cloud rajoute un paramètre dans l'URL contenant le motif d'abandon du parcours.
Les motifs retournés sont les suivants,
| Slug | Description |
| — | — |
canceled | L'utilisateur a quitté le parcours inopinément (fermeture du navigateur, rafraichissement de la page, …) |
expired_link | Le lien du PVID est expiré et ne permet plus d'effectuer le parcours |
no_camera | L'utilisateur n'a pas donné accès à sa caméra |
camera_resolution | La résolution de la caméra n'est pas suffisante |
browser_not_supported | Le navigateur utilisé n'est pas accepté pour le PVID |
browser_version | La version du navigateur utilisé n'est pas acceptée pour le PVID |
network | La connexion internet de l'utilisateur est insuffisante |
document_not_accepted | L'utilisateur ne possède aucun titre d'identité accepté pour le PVID |
recording_error | Nous avons rencontré une difficulté lors de la capture vidéo |
Exemple
L'accès à la caméra n'est pas autorisé : url_after_fail_or_abort?error=no_camera
Transmission via API
En plus d'être transmis dans l'URL url_after_fail_or_abort les motifs d'abandon sont également retournés via le paramètre abortedReason de la route GET /api/pvid/{id}.
5.4 Motifs de refus
Voici la liste des motifs de refus pour lesquels une vérification PVID peut être refusée.
| Slug | Description |
| — | — |
document_capture_bad_quality | Votre document d'identité n'a pas pu être analysé. Assurez-vous que votre pièce d'identité soit entièrement visible, bien cadrée et que la qualité vidéo permette de lire clairement les informations |
biometry_capture_bad_quality | La vérification n'a pas pu aboutir en raison de problèmes liés à la qualité de l'enregistrement ou à la reconnaissance du visage |
document_not_accepted | Le document présenté n'est pas accepté par notre service de vérification d'identité à distance |
document_expired | Le document présenté est expiré |
document_not_allowed | Votre document d'identité ne peut être accepté par nos services. Assurez-vous de présenter l'original (passeport, titre de séjour ou carte d'identité), en bon état sans étui ni obstruction visuelle. |
document_not_complete | Votre document n'a pas pu être entièrement analysé car une face est manquante ou mal cadrée |
action_not_complete | Les actions effectuées lors de la capture vidéo du document ou du visage n'ont pas été correctement réalisées et n'ont pas pu permettre la vérification de l'identité. Assurez-vous de bien respecter les consignes affichées lors du parcours (inclinaison et positionnement dans le cadre). |
biometry_not_conform | Les conditions dans lesquelles vous avez effectué votre vérification d'identité ne permettent pas de la traiter. Assurez-vous que votre visage soit entièrement visible, sans lunettes de soleil, verres teintés ou masque. |
fraud_is_suspected | Les conditions dans lesquelles vous avez effectué votre vérification d'identité ne permettent pas à notre service de la traiter |
period_expired | Un problème technique a empêché le traitement de votre vérification d'identité dans les delais impartis |
technical_error | Un problème technique a empêché le traitement de votre vérification d'identité |
