Vérification d'une identité particulier contre la base d'enregistrements individus FR. Score 0-100 par champ + score global agrégé.
Headers requis
| Header | Valeur | Requis | Description |
|---|---|---|---|
| X-API-Key | vkey-... | oui | Clé d'authentification du compte (sinon HTTP 401 missing_api_key) |
| Content-Type | application/json | oui | Type du corps de requête JSON. Le corps est parsé en JSON quelle que soit la valeur. |
Body
| Champ | Type | Requis | Description |
|---|---|---|---|
| reference | string | non | Identifiant client retourné tel quel |
| person.first_name | string | oui | Prénom |
| person.last_name | string | oui | Nom de famille |
| person.gender | M | F | non | Civilité |
| person.birth_date | string | non* | Format ISO 8601 strict YYYY-MM-DD (sinon HTTP 400 invalid_birth_date) |
| person.address.street | string | non* | Rue + numéro |
| person.address.city | string | non* | Ville |
| person.address.postal_code | string | non* | 5 chiffres (zéro-padding auto) |
| person.email | string | non* | Email (validation format) |
| person.mobile | string | non* | Mobile FR strict (sinon HTTP 400 invalid_mobile) |
| person.landline | string | non* | Fixe FR strict (sinon HTTP 400 invalid_landline) |
| person.phone | string | non* | Téléphone FR (auto-détection mobile/fixe). Renvoie matches.phone.resolved_as = mobile | landline |
| match_rule | object | non | Règle booléenne optionnelle : arbre {"and":[...]} / {"or":[...]} sur les champs (+ alias address = street ET city ET postal_code). Renvoie rule.passed. Malformée -> HTTP 400 invalid_match_rule. |
| min_score | integer | non | Seuil 1-100 (défaut 50) : un champ est satisfait si son score ≥ min_score. Pris en compte avec match_rule seulement. Hors plage -> HTTP 400 invalid_min_score. |
* Au moins un critère discriminant requis en plus de first_name + last_name : birth_date, email, mobile, landline, phone, ou une adresse complète (address.street + address.city + address.postal_code). Sinon HTTP 400 insufficient_data.
Les champs email, mobile, landline et phone acceptent la valeur en clair OU son empreinte MD5 (32 hex) / SHA256 (64 hex), auto-détectée. L'empreinte porte sur la forme canonique (email en minuscules ; téléphone national FR à 10 chiffres). Un champ haché ne renvoie que match ou no_match.
Réponse 200
| Champ | Type | Description |
|---|---|---|
| transaction_id | string | Identifiant unique de la transaction |
| reference | string | null | Référence fournie par le client (passthrough) |
| score | number | null | Score global 0-100 (moyenne des champs avec status match / partial / no_match). Les statuts missing et not_searched sont exclus du calcul. Plafonné à 50 si ambiguous=true. |
| ambiguous | boolean | true si plusieurs profils distincts matchent les critères fournis (homonymes au même nom+adresse avec contacts divergents). Le client doit fournir plus de contacts (email, mobile, DOB) pour lever l'ambiguïté. |
| matches[field].status | enum | match, partial, no_match, not_searched, missing |
| matches[field].score | number | null | Score 0-100 pour ce champ (null si missing ou not_searched) |
| timing_ms | number | Durée du traitement côté serveur |
| rule | object | Présent uniquement si match_rule est fourni : {passed, min_score} (verdict de la règle ; n'altère ni matches ni score). |
Exemple
curl -X POST https://verify.zecible.fr/api/person \ -H 'X-API-Key: vkey-...' \ -H 'Content-Type: application/json' \ -d '{ "reference": "tx-001", "person": { "first_name": "Jean", "last_name": "Dupont", "birth_date": "1980-05-15" } }'
const r = await fetch('https://verify.zecible.fr/api/person', { method: 'POST', headers: { 'X-API-Key': process.env.VERIFY_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ reference: 'tx-001', person: { first_name: 'Jean', last_name: 'Dupont', birth_date: '1980-05-15' } }) }); const data = await r.json(); console.log(data.score, data.matches);
import requests, os r = requests.post( 'https://verify.zecible.fr/api/person', headers={'X-API-Key': os.environ['VERIFY_KEY']}, json={ 'reference': 'tx-001', 'person': {'first_name': 'Jean', 'last_name': 'Dupont', 'birth_date': '1980-05-15'} } ) print(r.json()['score'])
<?php $ch = curl_init('https://verify.zecible.fr/api/person'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'X-API-Key: ' . getenv('VERIFY_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'reference' => 'tx-001', 'person' => ['first_name' => 'Jean', 'last_name' => 'Dupont', 'birth_date' => '1980-05-15'], ]), ]); $data = json_decode(curl_exec($ch), true); echo $data['score'];
Vérification d'une entreprise FR (KYB) contre la base B2B : SIRET, raison sociale, contacts, dirigeants. Score 0-100 par champ.
Headers requis
| Header | Valeur | Requis | Description |
|---|---|---|---|
| X-API-Key | vkey-... | oui | Clé d'authentification du compte (sinon HTTP 401 missing_api_key) |
| Content-Type | application/json | oui | Type du corps de requête JSON. Le corps est parsé en JSON quelle que soit la valeur. |
Body
| Champ | Type | Requis | Description |
|---|---|---|---|
| reference | string | non | Identifiant client retourné tel quel |
| business.siret | string | non* | 14 chiffres (établissement) |
| business.siren | string | non* | 9 chiffres (entité juridique) |
| business.vat_intracom | string | non | N° TVA intracom (dérivé du SIREN, vérifié par clé) |
| business.name | string | non* | Raison sociale |
| business.legal_form | string | non | Forme juridique (SAS, SARL, EURL...) |
| business.naf | string | non | Code NAF / APE (ex : 7311Z) |
| business.address.street | string | non | Rue + numéro |
| business.address.city | string | non | Ville |
| business.address.postal_code | string | non | 5 chiffres |
| business.email | string | non | Email de contact |
| business.mobile | string | non | Mobile FR strict (sinon HTTP 400 invalid_mobile) |
| business.landline | string | non | Fixe FR strict (sinon HTTP 400 invalid_landline) |
| business.phone | string | non | Téléphone FR (auto-détection mobile/fixe). Renvoie matches.business.phone.resolved_as = mobile | landline |
| business.website | string | non | URL du site web |
| executives[N].first_name | string | non | Prénom du dirigeant ou contact |
| executives[N].last_name | string | non | Nom du dirigeant ou contact |
| executives[N].role | string | non | Fonction (président, gérant, directeur, etc.) |
* Au moins un parmi : business.siret, business.siren, ou business.name + (business.address.city OU business.address.postal_code). executives[] : jusqu'à 10 entrées par requête (sinon HTTP 400 too_many_executives). Scoring best-match indépendant pour chaque entrée contre tous les contacts du SIRET de la société matchée (cap 100 contacts/SIRET). En cas d'ambiguous=true, le pool reste limité au Niveau 1 du pipeline.
Les champs siren, email, mobile, landline et phone acceptent la valeur en clair OU son empreinte MD5 (32 hex) / SHA256 (64 hex), auto-détectée (siren : 9 chiffres bruts). Un champ haché ne renvoie que match ou no_match.
Réponse 200
| Champ | Type | Description |
|---|---|---|
| transaction_id | string | Identifiant unique de la transaction |
| reference | string | null | Référence fournie par le client (passthrough) |
| score | number | null | Score global 0-100 (moyenne des champs avec status match / partial / no_match). Les statuts missing et not_searched sont exclus du calcul. Plafonné à 50 si ambiguous=true. |
| ambiguous | boolean | true si plusieurs entreprises distinctes matchent les critères fournis (établissements du même groupe ou homonymes au même nom+adresse avec contacts divergents). Le client doit fournir SIRET ou un contact unique (email, phone, website) pour lever l'ambiguïté. |
| matches.business.<field>.status | enum | match, partial, no_match, not_searched, missing |
| matches.business.<field>.score | number | null | Score 0-100 du champ (null si missing ou not_searched) |
| matches.executives | array | Toujours présent (vide si aucun dirigeant en input). Ordre préservé = ordre d'input. |
| matches.executives[N].<field> | object | Statut + score par champ pour chaque dirigeant input (best-match indépendant, même enum que business) |
| timing_ms | number | Durée du traitement côté serveur |
Exemple
curl -X POST https://verify.zecible.fr/api/business \ -H 'X-API-Key: vkey-...' \ -H 'Content-Type: application/json' \ -d '{ "reference": "tx-biz-001", "business": { "siret": "00000000000000", "name": "ACME SAS", "legal_form": "SAS" }, "executives": [ { "first_name": "Jean", "last_name": "DUPONT", "role": "président" } ] }'
const r = await fetch('https://verify.zecible.fr/api/business', { method: 'POST', headers: { 'X-API-Key': process.env.VERIFY_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ reference: 'tx-biz-001', business: { siret: '00000000000000', name: 'ACME SAS' }, executives: [{ first_name: 'Jean', last_name: 'DUPONT' }] }) }); console.log((await r.json()).score);
import requests, os r = requests.post( 'https://verify.zecible.fr/api/business', headers={'X-API-Key': os.environ['VERIFY_KEY']}, json={ 'reference': 'tx-biz-001', 'business': {'siret': '00000000000000', 'name': 'ACME SAS'}, 'executives': [{'first_name': 'Jean', 'last_name': 'DUPONT'}] } ) print(r.json()['score'])
<?php $ch = curl_init('https://verify.zecible.fr/api/business'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'X-API-Key: ' . getenv('VERIFY_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'reference' => 'tx-biz-001', 'business' => ['siret' => '00000000000000', 'name' => 'ACME SAS'], 'executives' => [['first_name' => 'Jean', 'last_name' => 'DUPONT']], ]), ]); $data = json_decode(curl_exec($ch), true); echo $data['score'];
Reporting de consommation : nombre de requêtes + breakdown par produit facturable + total HT.
Headers requis
| Header | Valeur | Requis | Description |
|---|---|---|---|
| X-API-Key | vkey-... | oui | Clé d'authentification du compte (sinon HTTP 401 missing_api_key) |
Query params
| Param | Type | Requis | Description |
|---|---|---|---|
| start | string | non | Date début YYYY-MM-DD (défaut : 1er jour mois courant) |
| stop | string | non | Date fin YYYY-MM-DD (défaut : dernier jour mois courant) |
Réponse 200
| Champ | Type | Description |
|---|---|---|
| period | object | { start, stop } - période effectivement requêtée |
| requests | number | Nombre total de requêtes |
| succeeded | number | Requêtes avec au moins un produit facturable |
| failed | number | Requêtes sans aucun produit facturable |
| total_pretax | number | Total HT EUR sur la période |
| currency | string | Code devise (EUR) |
| products[] | array | Ventilation par produit (key, label, count, cpm_pretax, total_pretax) |
Exemple
curl 'https://verify.zecible.fr/api/usage?start=2026-05-01&stop=2026-05-31' \ -H 'X-API-Key: vkey-...'
Compteur global par clé API, fenêtre fixe d'1 minute, partagé entre /api/person, /api/business et /api/usage. Limite par défaut : 60 requêtes par minute. Surchargeable per-account (nous contacter pour les besoins entreprise).
Headers de réponse (toutes les réponses)
| Header | Description |
|---|---|
| X-RateLimit-Limit | Quota maximum pour la fenêtre courante (ex : 60). 0 = compte sans limite. |
| X-RateLimit-Remaining | Quota restant après cette requête. -1 si le compte est sans limite. |
| X-RateLimit-Reset | Timestamp Unix UTC du prochain reset (début de la prochaine minute). 0 si compte sans limite. |
| Retry-After | Présent uniquement sur HTTP 429. Nombre de secondes avant le reset de la fenêtre. |
Réponse HTTP 429 (quota dépassé)
| Champ | Description |
|---|---|
| error | "rate_limit_exceeded" |
| message | Message lisible avec le nombre de secondes avant le prochain reset. |
| limit | Quota du compte. |
| reset_unix | Timestamp Unix UTC du prochain reset. |
Stratégie recommandée
- Lire
X-RateLimit-Remainingà chaque réponse pour anticiper le throttling côté client. - En cas de HTTP 429, respecter
Retry-Afteravant de réessayer (backoff exponentiel inutile : la fenêtre reset à la minute exacte). - Pour des volumes > 60 req/min, demander un quota relevé : nous contacter.
- Le compteur démarre à la première requête de chaque minute UTC, indépendamment de l'endpoint appelé.
Exemple de réponse 429
# HTTP 429 Too Many Requests # Headers X-RateLimit-Limit: 60 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1779994620 Retry-After: 42 # Body { "error": "rate_limit_exceeded", "message": "Limite de 60 requetes par minute atteinte. Reessayer dans 42s.", "limit": 60, "reset_unix": 1779994620 }
Liste exhaustive des codes d'erreur renvoyés par l'API. Format de réponse uniforme : {"error": "code_machine", "message": "explication humaine"}. Toujours en JSON, charset UTF-8.
Authentification (401)
| Code | Endpoint(s) | Cause |
|---|---|---|
| missing_api_key | tous | Header X-API-Key absent ou vide. |
| invalid_api_key | tous | Clé inconnue ou révoquée. Vérifier la valeur exacte (sensible à la casse) et qu'elle correspond bien à un compte actif. |
| account_disabled | tous | Compte supprimé ou désactivé côté Zecible. Contacter le support pour réactivation. |
| ip_not_allowed | tous | IP appelante absente de la whitelist du compte. Le message contient l'IP rejetée. Ajouter l'IP via le support. |
Méthode HTTP (405)
| Code | Endpoint(s) | Cause |
|---|---|---|
| method_not_allowed | tous | POST attendu sur /api/person et /api/business. GET attendu sur /api/usage. |
Validation du body (400)
| Code | Endpoint(s) | Cause |
|---|---|---|
| invalid_body | person, business | Body absent, JSON malformé, ou root non-objet. |
| invalid_reference | person, business | Le champ reference dépasse 256 caractères. Raccourcir le passthrough client. |
| missing_required_fields | person | person.first_name et person.last_name sont obligatoires. |
| missing_required_fields | business | Au moins un parmi business.siret, business.siren, ou business.name + business.address.city OU business.address.postal_code. |
| insufficient_data | person | Identité fournie mais aucun critère discriminant (birth_date, téléphone, email ou adresse complète) pour identifier une cible. |
| insufficient_data | business | Aucune combinaison exploitable (SIRET / SIREN / nom + adresse / nom + contact). |
| invalid_birth_date | person | person.birth_date n'est pas au format ISO 8601 strict YYYY-MM-DD. |
| invalid_mobile | person, business | Le champ mobile doit être un numéro mobile FR (10 chiffres débutant par 06 ou 07). |
| invalid_landline | person, business | Le champ landline doit être un numéro fixe FR (pas un mobile). |
| invalid_phone | person, business | Le champ phone doit être un numéro de téléphone FR valide (10 chiffres). |
| invalid_email | person, business | Le champ email doit être une adresse email valide. |
| invalid_gender | person | Le champ gender doit être M ou F. |
| invalid_siret | business | Le champ siret doit être un SIRET valide (14 chiffres, clé de Luhn). |
| invalid_siren | business | Le champ siren doit être un SIREN valide (9 chiffres, clé de Luhn). |
| too_many_executives | business | executives[] contient plus de 10 entrées. Limiter la requête à 10 dirigeants ou en chaîner plusieurs. |
Taille du payload (413)
| Code | Endpoint(s) | Cause |
|---|---|---|
| body_too_large | person, business | Body de requête supérieur à 32 KB. Réduire la taille du JSON (ex : limiter le nombre de dirigeants, raccourcir les champs texte). |
Quotas (429)
| Code | Endpoint(s) | Cause |
|---|---|---|
| rate_limit_exceeded | tous | Quota par minute atteint. Voir la section Rate limiting pour la stratégie de retry et les headers X-RateLimit-*. |
Crédits / budget (402)
| Code | Endpoint(s) | Cause |
|---|---|---|
| quota_exceeded | person, business | Plafond de crédits de la période atteint (budget mensuel ou portefeuille épuisable du compte). Les headers X-Credit-Limit / X-Credit-Used / X-Credit-Remaining (EUR HT) indiquent le budget le plus contraignant. Contacter le support pour relever le plafond. |
Format de réponse
# HTTP 4xx # Content-Type: application/json; charset=UTF-8 { "error": "invalid_birth_date", "message": "person.birth_date doit etre au format ISO 8601 (YYYY-MM-DD)." }
Le champ error est stable et destiné au traitement programmatique. Le champ message est destiné à l'affichage et peut évoluer entre versions.