API Console - Documentation interactive
POST /api/person

Vérification d'une identité particulier contre la base d'enregistrements individus FR. Score 0-100 par champ + score global agrégé.

database
Base+220M enregistrements individus FR (multi-sources)
speed
LatenceRéponse synchrone en temps réel, au format JSON
warning
Cas ambiguSignal informatif, n'altère pas le score

Headers requis

HeaderValeurRequisDescription
X-API-Keyvkey-...ouiClé d'authentification du compte (sinon HTTP 401 missing_api_key)
Content-Typeapplication/jsonouiType du corps de requête JSON. Le corps est parsé en JSON quelle que soit la valeur.

Body

ChampTypeRequisDescription
referencestringnonIdentifiant client retourné tel quel
person.first_namestringouiPrénom
person.last_namestringouiNom de famille
person.genderM | FnonCivilité
person.birth_datestringnon*YYYY-MM-DD recommandé (formats de date courants tolérés). Date non valide : champ ignoré et signalé status:"invalid" (invalid_birth_date), réponse 200.
person.birth_yearstringnonAnnée de naissance (AAAA). Alternative décomposée à birth_date (style Trulioo) ; l'année est le critère discriminant requis.
person.birth_monthstringnonMois de naissance (MM, 01-12). Optionnel, avec birth_year.
person.birth_daystringnonJour de naissance (JJ, 01-31). Optionnel, avec birth_year.
person.address.streetstringnon*Rue + numéro
person.address.citystringnon*Ville
person.address.postal_codestringnon*5 chiffres (zéro-padding auto)
person.emailstringnon*Email (validation format)
person.mobilestringnon*Mobile FR strict (06/07). Invalide : champ ignoré et signalé status:"invalid" (invalid_mobile), réponse 200.
person.landlinestringnon*Fixe FR strict. Invalide : champ ignoré et signalé status:"invalid" (invalid_landline), réponse 200.
person.phonestringnon*Téléphone FR (auto-détection mobile/fixe). Renvoie matches.phone.resolved_as = mobile | landline
name_thresholdnumbernonSeuil de matching flou des NOMS de personne (prénom et nom uniquement) : 0.5-1.0 (défaut 0.90, ou la valeur du compte). Un nom score 0 sous ce seuil de similarité Jaro-Winkler. Baissez-le pour récupérer des noms approchants, montez-le pour être plus strict. Hors plage -> HTTP 400 invalid_name_threshold.
match_ruleobjectnonRè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_scoreintegernonSeuil 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

ChampTypeDescription
transaction_idstringIdentifiant unique de la transaction
referencestring | nullRéférence fournie par le client (passthrough)
scorenumber | nullScore global 0-100 (moyenne des champs avec status match / partial / no_match). Les statuts missing et not_searched sont exclus du calcul. Signal brut, non plafonné.
confidencenumber | nullIndice de confiance global 0-100 dans l'identification : force du faisceau d'identité (pouvoir discriminant des champs qui correspondent, qui sature), unicité de la cible (pénalisée si homonymes sans contact unique fourni) et couverture. null si aucune fiche trouvée. Complète le score sans le remplacer : un score de 100 sur un homonyme ressort en confiance basse.
confidence_levelenum | nullPalier lisible de confidence : high (≥70, identification fiable), medium (40-69) ou low (<40, à revoir : peu de discriminant ou homonyme). null si aucune fiche.
ambiguousbooleantrue si plusieurs profils distincts matchent les critères fournis (homonymes au même nom+adresse avec contacts divergents). Signal informatif, n'altère pas le score : le client doit fournir plus de contacts (email, mobile, DOB) pour lever l'ambiguïté.
matches[field].statusenummatch, partial, no_match, not_searched, missing, invalid
matches[field].scorenumber | nullScore 0-100 pour ce champ (null si missing ou not_searched)
timing_msnumberDurée du traitement côté serveur
ruleobjectPrésent uniquement si match_rule est fourni : {match_rule, passed, min_score} (règle évaluée + verdict ; n'altère ni matches ni score).

Code

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'];

infoPaires requête / réponse réelles, figées depuis le bac à sable de démonstration (données 100% fictives, aucune identité réelle). Aucune clé API requise.

renvoyé tel quel dans la réponsetag
prénom (accents acceptés)person
nom de famille (accents acceptés)badge
M masculin / F fémininwc
YYYY-MM-DD recommandé (ex 1980-05-15)cake
année AAAA (alternative à la date)cake
mois MM (01-12, optionnel)cake
jour JJ (01-31, optionnel)cake
rue + numérosignpost
libellé BAN normalisélocation_city
5 chiffres (zero-pad auto si < 5)markunread_mailbox
format RFC 5321 (un seul email)alternate_email
FR 10 chiffres - auto mobile/fixe (renvoie resolved_as)phone_iphone
FR 10 chiffres mobile strict (06/07)smartphone
FR 10 chiffres fixe strict (01-05, 09)call
Seuil nom 0.5-1.0 (défaut 0.90)match_case
Règle ET/OU optionnelle
Seuil 1-100 (défaut 50)speed

infoDonnées de démonstration fictives (aucune identité réelle).

Réponse
Soumettez le formulaire pour voir la réponse JSON.
POST /api/business BETA

Vérification d'une entreprise FR (KYB) contre la base B2B : SIRET, raison sociale, contacts, dirigeants. Score 0-100 par champ.

database
Base+55M enregistrements entreprises FR
speed
LatenceRéponse synchrone en temps réel, au format JSON
manage_accounts
DirigeantsJusqu'à 10 par requête, best-match indépendant
warning
Cas ambiguScore plafonné à 50 si SIRET multiples

Headers requis

HeaderValeurRequisDescription
X-API-Keyvkey-...ouiClé d'authentification du compte (sinon HTTP 401 missing_api_key)
Content-Typeapplication/jsonouiType du corps de requête JSON. Le corps est parsé en JSON quelle que soit la valeur.

Body

ChampTypeRequisDescription
referencestringnonIdentifiant client retourné tel quel
business.siretstringnon*14 chiffres (établissement)
business.sirenstringnon*9 chiffres (entité juridique)
business.vat_intracomstringnonN° TVA intracom (dérivé du SIREN, vérifié par clé)
business.namestringnon*Raison sociale
business.legal_formstringnonForme juridique (SAS, SARL, EURL...)
business.nafstringnonCode NAF / APE (ex : 7311Z)
business.address.streetstringnonRue + numéro
business.address.citystringnonVille
business.address.postal_codestringnon5 chiffres
business.emailstringnonEmail de contact
business.mobilestringnonMobile FR strict (06/07). Invalide : champ ignoré et signalé status:"invalid" (invalid_mobile), réponse 200.
business.landlinestringnonFixe FR strict. Invalide : champ ignoré et signalé status:"invalid" (invalid_landline), réponse 200.
business.phonestringnonTéléphone FR (auto-détection mobile/fixe). Renvoie matches.business.phone.resolved_as = mobile | landline
business.websitestringnonURL du site web
executives[N].first_namestringnonPrénom du dirigeant ou contact
executives[N].last_namestringnonNom du dirigeant ou contact
executives[N].rolestringnonFonction (président, gérant, directeur, etc.)
name_thresholdnumbernonSeuil de matching flou des NOMS de dirigeants (prénom et nom des executives[], pas la raison sociale) : 0.5-1.0 (défaut 0.90, ou la valeur du compte). Hors plage -> HTTP 400 invalid_name_threshold.

* 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

ChampTypeDescription
transaction_idstringIdentifiant unique de la transaction
referencestring | nullRéférence fournie par le client (passthrough)
scorenumber | nullScore 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.
ambiguousbooleantrue 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>.statusenummatch, partial, no_match, not_searched, missing, invalid
matches.business.<field>.scorenumber | nullScore 0-100 du champ (null si missing ou not_searched)
matches.executivesarrayToujours présent (vide si aucun dirigeant en input). Ordre préservé = ordre d'input.
matches.executives[N].<field>objectStatut + score par champ pour chaque dirigeant input (best-match indépendant, même enum que business)
timing_msnumberDurée du traitement côté serveur

Code

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'];

infoPaires requête / réponse réelles, figées depuis le bac à sable de démonstration (données 100% fictives, aucune identité réelle). Aucune clé API requise.

renvoyé tel quel dans la réponsetag
Seuil nom 0.5-1.0 (défaut 0.90)match_case
14 chiffres (espaces tolérés)fingerprint
9 chiffres (entité juridique)fingerprint
FR + 11 chiffres (calculé depuis SIREN)payments
raison sociale ou enseigne (fuzzy)storefront
SAS, SARL, EURL, SA, SCI...gavel
4 chiffres + 1 lettre (ex 7311Z)category
ex : 12 rue de la Paixsignpost
ville de l'établissementlocation_city
5 chiffresmarkunread_mailbox
email de contact (RFC 5321)alternate_email
FR 10 chiffres (auto mobile/fixe)call
URL complète (https:// inclus)language
FR 10 chiffres mobile strict (06/07)smartphone
FR 10 chiffres fixe strict (01-05, 09)call
prénom (accents OK)person
nom de famille (accents OK)badge
président, gérant, directeur (fuzzy)work

infoDonnées de démonstration fictives (aucune identité réelle).

Réponse
Soumettez le formulaire pour voir la réponse JSON.
GET /api/usage

Reporting de consommation : nombre de requêtes + breakdown par produit facturable + total HT.

person
ScopeUtilisateur API du caller (clé courante)
date_range
Période défautMois en cours (1er → dernier jour)
euro
DeviseEUR HT, prorata réel par produit

Headers requis

HeaderValeurRequisDescription
X-API-Keyvkey-...ouiClé d'authentification du compte (sinon HTTP 401 missing_api_key)

Query params

ParamTypeRequisDescription
startstringnonDate début YYYY-MM-DD (défaut : 1er jour mois courant)
stopstringnonDate fin YYYY-MM-DD (défaut : dernier jour mois courant)

Réponse 200

ChampTypeDescription
periodobject{ start, stop } - période effectivement requêtée
requestsnumberNombre total de requêtes
succeedednumberRequêtes avec au moins un produit facturable
no_matchnumberRequêtes exécutées sans aucun produit facturable (recherche faite, rien matché)
errorednumberRequêtes en erreur 4xx (non facturées)
failednumberRétro-compat : no_match + errored (requêtes non facturées)
errors_completeobject{ code: count } des erreurs 4xx complètes (rejets globaux)
errors_partialobject{ code: count } des codes invalid_* par-champ (réponses 200, non exclusif des états)
total_pretaxnumberTotal HT EUR sur la période
currencystringCode devise (EUR)
products[]arrayVentilation par produit (key, label, count, cpm_pretax, total_pretax)
timing_msnumberTemps de traitement serveur en millisecondes

Code

curl 'https://verify.zecible.fr/api/usage?start=2026-05-01&stop=2026-05-31' \
  -H 'X-API-Key: vkey-...'
YYYY-MM-DD - défaut : 1er jour du mois en coursevent_available
YYYY-MM-DD - défaut : dernier jour du mois en coursevent_busy

infoDonnées de démonstration fictives (aucune identité réelle).

Réponse
Soumettez le formulaire pour voir la réponse JSON.
429 Rate limiting

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)

HeaderDescription
X-RateLimit-LimitQuota maximum pour la fenêtre courante (ex : 60). 0 = compte sans limite.
X-RateLimit-RemainingQuota restant après cette requête. -1 si le compte est sans limite.
X-RateLimit-ResetTimestamp Unix UTC du prochain reset (début de la prochaine minute). 0 si compte sans limite.
Retry-AfterPrésent uniquement sur HTTP 429. Nombre de secondes avant le reset de la fenêtre.

Réponse HTTP 429 (quota dépassé)

ChampDescription
error"rate_limit_exceeded"
messageMessage lisible avec le nombre de secondes avant le prochain reset.
limitQuota du compte.
reset_unixTimestamp 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-After avant 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
}
4XX Codes d'erreur

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)

CodeEndpoint(s)Cause
missing_api_keytousHeader X-API-Key absent ou vide.
invalid_api_keytousClé inconnue ou révoquée. Vérifier la valeur exacte (sensible à la casse) et qu'elle correspond bien à un compte actif.
account_disabledtousCompte supprimé ou désactivé côté Zecible. Contacter le support pour réactivation.
ip_not_allowedtousIP appelante absente de la whitelist du compte. Le message contient l'IP rejetée. Ajouter l'IP via le support.

Méthode HTTP (405)

CodeEndpoint(s)Cause
method_not_allowedtousPOST attendu sur /api/person et /api/business. GET attendu sur /api/usage.

Validation du body (400)

CodeEndpoint(s)Cause
invalid_bodyperson, businessBody absent, JSON malformé, ou root non-objet.
invalid_referenceperson, businessLe champ reference doit être une chaîne (passthrough) d'au plus 256 caractères.
missing_required_fieldspersonperson.first_name et person.last_name sont obligatoires.
missing_required_fieldsbusinessAu moins un parmi business.siret, business.siren, ou business.name + business.address.city OU business.address.postal_code.
insufficient_datapersonIdentité fournie mais aucun critère discriminant (birth_date, téléphone, email ou adresse complète) pour identifier une cible.
insufficient_databusinessAucune combinaison exploitable (SIRET / SIREN / nom + adresse / nom + contact).
too_many_executivesbusinessexecutives[] contient plus de 10 entrées. Limiter la requête à 10 dirigeants ou en chaîner plusieurs.
invalid_match_rulepersonLe champ match_rule est malformé (arbre {and|or:[...]} imbriqué attendu, sur des champs person connus).
invalid_min_scorepersonLe champ min_score doit être un entier entre 1 et 100.
invalid_name_thresholdperson/businessLe champ name_threshold doit être un nombre entre 0.5 et 1.0.

Validation par champ (réponse 200, statut invalid)

Ces codes ne sont pas des erreurs HTTP : la requête renvoie 200. Le champ invalide est ignoré (exclu de la recherche) et signalé dans matches.<champ>.status = invalid, le code étant repris dans error. Cas particulier : si un siret/siren invalide ne laisse aucun identifiant exploitable, la requête est alors rejetée en missing_required_fields / insufficient_data (400).

CodeEndpoint(s)Cause
invalid_birth_datepersonperson.birth_date n'est pas une date valide (YYYY-MM-DD recommandé).
invalid_mobileperson, businessLe champ mobile doit être un numéro mobile FR (10 chiffres débutant par 06 ou 07).
invalid_landlineperson, businessLe champ landline doit être un numéro fixe FR (pas un mobile).
invalid_phoneperson, businessLe champ phone doit être un numéro de téléphone FR valide (10 chiffres).
invalid_emailperson, businessLe champ email doit être une adresse email valide.
invalid_genderpersonLe champ gender doit être M ou F.
invalid_siretbusinessLe champ siret doit être un SIRET valide (14 chiffres, clé de Luhn).
invalid_sirenbusinessLe champ siren doit être un SIREN valide (9 chiffres, clé de Luhn).

Taille du payload (413)

CodeEndpoint(s)Cause
body_too_largeperson, businessBody 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)

CodeEndpoint(s)Cause
rate_limit_exceededtousQuota par minute atteint. Voir la section Rate limiting pour la stratégie de retry et les headers X-RateLimit-*.

Crédits / budget (402)

CodeEndpoint(s)Cause
quota_exceededperson, businessPlafond 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.