| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
Verify an individual's identity against the FR individual records database. 0-100 score per field + aggregated global score.
| Header | Value | Required | Description |
|---|---|---|---|
| X-API-Key | vkey-... | yes | Account authentication key (otherwise HTTP 401 missing_api_key) |
| Content-Type | application/json | yes | Request body content type JSON. The body is parsed as JSON regardless of the value. |
| Field | Type | Required | Description |
|---|---|---|---|
| reference | string | no | Client identifier returned as-is |
| person.first_name | string | yes | First name |
| person.last_name | string | yes | Last name |
| person.gender | M | F | no | Gender |
| person.birth_date | string | no* | YYYY-MM-DD recommended (common date formats accepted). Invalid date: field ignored and reported as status:"invalid" (invalid_birth_date), HTTP 200. |
| person.birth_year | string | no | Year of birth (YYYY). Decomposed alternative to birth_date (Trulioo-style); the year is the required discriminator. |
| person.birth_month | string | no | Month of birth (MM, 01-12). Optional, alongside birth_year. |
| person.birth_day | string | no | Day of birth (DD, 01-31). Optional, alongside birth_year. |
| person.address.street | string | no* | Street + number |
| person.address.city | string | no* | City |
| person.address.postal_code | string | no* | 5 digits (auto zero-padding) |
| person.email | string | no* | Email (format validation) |
| person.mobile | string | no* | Strict FR mobile (06/07). If invalid: field ignored and reported as status:"invalid" (invalid_mobile), HTTP 200. |
| person.landline | string | no* | Strict FR landline. If invalid: field ignored and reported as status:"invalid" (invalid_landline), HTTP 200. |
| person.phone | string | no* | FR phone (auto-detect mobile/landline). Returns matches.phone.resolved_as = mobile | landline |
| name_threshold | number | no | Fuzzy-matching gate for person NAMES (first_name and last_name only): 0.5-1.0 (default 0.90, or the account value). A name scores 0 below this Jaro-Winkler similarity. Lower it to recover approximate names, raise it to be stricter. Out of range -> HTTP 400 invalid_name_threshold. |
| match_rule | object | no | Optional boolean rule: {"and":[...]} / {"or":[...]} tree over the fields (+ alias address = street AND city AND postal_code). Returns rule.passed. Malformed -> HTTP 400 invalid_match_rule. |
| min_score | integer | no | Threshold 1-100 (default 50): a field is satisfied if its score ≥ min_score. Considered with match_rule only. Out of range -> HTTP 400 invalid_min_score. |
* At least one discriminating criterion required in addition to first_name + last_name: birth_date, email, mobile, landline, phone, or a full address (address.street + address.city + address.postal_code). Otherwise HTTP 400 insufficient_data.
The email, mobile, landline and phone fields accept the clear-text value OR its MD5 (32 hex) / SHA256 (64 hex) digest, auto-detected. The digest is computed on the canonical form (lowercase email; 10-digit French national phone number). A hashed field returns only match or no_match.
| Field | Type | Description |
|---|---|---|
| transaction_id | string | Unique transaction identifier |
| reference | string | null | Reference provided by the client (passthrough) |
| score | number | null | Global score 0-100 (average of fields with status match / partial / no_match). Statuses missing and not_searched are excluded from the calculation. Raw signal, not capped. |
| confidence | number | null | Global confidence 0-100 in the identification: strength of the identity evidence (discriminating power of the matching fields, which saturates), uniqueness of the target (penalised for homonyms when no unique contact is provided) and coverage. null if no record found. Complements score rather than replacing it: a score of 100 on a homonym yields a low confidence. |
| confidence_level | enum | null | Readable band of confidence: high (≥70, reliable identification), medium (40-69) or low (<40, to review: little discriminating signal or homonym). null if no record. |
| ambiguous | boolean | true if several distinct profiles match the provided criteria (homonyms at the same name+address with divergent contacts). Informational signal, does not alter the score: the client must provide more contacts (email, mobile, DOB) to resolve the ambiguity. |
| matches[field].status | enum | match, partial, no_match, not_searched, missing, invalid |
| matches[field].score | number | null | Score 0-100 for this field (null if missing or not_searched) |
| timing_ms | number | Server-side processing time |
| rule | object | Present only if match_rule is provided: {match_rule, passed, min_score} (evaluated rule + verdict; does not alter matches or score). |
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'];
infoReal request / response pairs, frozen from the demo sandbox (100% fictitious data, no real identity). No API key required.
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
Verify a FR business (KYB) against the B2B database: SIRET, company name, contacts, executives. 0-100 score per field.
| Header | Value | Required | Description |
|---|---|---|---|
| X-API-Key | vkey-... | yes | Account authentication key (otherwise HTTP 401 missing_api_key) |
| Content-Type | application/json | yes | Request body content type JSON. The body is parsed as JSON regardless of the value. |
| Field | Type | Required | Description |
|---|---|---|---|
| reference | string | no | Client identifier returned as-is |
| business.siret | string | no* | 14 digits (establishment) |
| business.siren | string | no* | 9 digits (legal entity) |
| business.vat_intracom | string | no | Intra-EU VAT number (derived from SIREN, verified by key) |
| business.name | string | no* | Company name |
| business.legal_form | string | no | Legal form (SAS, SARL, EURL...) |
| business.naf | string | no | NAF / APE code (e.g. 7311Z) |
| business.address.street | string | no | Street + number |
| business.address.city | string | no | City |
| business.address.postal_code | string | no | 5 digits |
| business.email | string | no | Contact email |
| business.mobile | string | no | Strict FR mobile (06/07). If invalid: field ignored and reported as status:"invalid" (invalid_mobile), HTTP 200. |
| business.landline | string | no | Strict FR landline. If invalid: field ignored and reported as status:"invalid" (invalid_landline), HTTP 200. |
| business.phone | string | no | FR phone (auto-detect mobile/landline). Returns matches.business.phone.resolved_as = mobile | landline |
| business.website | string | no | Website URL |
| executives[N].first_name | string | no | Executive or contact first name |
| executives[N].last_name | string | no | Executive or contact last name |
| executives[N].role | string | no | Role (president, manager, director, etc.) |
| name_threshold | number | no | Fuzzy-matching gate for EXECUTIVE names (first_name and last_name of executives[], not the company name): 0.5-1.0 (default 0.90, or the account value). Out of range -> HTTP 400 invalid_name_threshold. |
* At least one of: business.siret, business.siren, or business.name + (business.address.city OR business.address.postal_code). executives[]: up to 10 entries per request (otherwise HTTP 400 too_many_executives). Independent best-match scoring for each entry against all contacts of the matched company's SIRET (cap 100 contacts/SIRET). When ambiguous=true, the pool is limited to Level 1 of the pipeline.
The siren, email, mobile, landline and phone fields accept the clear-text value OR its MD5 (32 hex) / SHA256 (64 hex) digest, auto-detected (siren: 9 raw digits). A hashed field returns only match or no_match.
| Field | Type | Description |
|---|---|---|
| transaction_id | string | Unique transaction identifier |
| reference | string | null | Reference provided by the client (passthrough) |
| score | number | null | Global score 0-100 (average of fields with status match / partial / no_match). Statuses missing and not_searched are excluded from the calculation. Capped at 50 if ambiguous=true. |
| ambiguous | boolean | true if several distinct businesses match the provided criteria (establishments of the same group or homonyms at the same name+address with divergent contacts). The client must provide a SIRET or a unique contact (email, phone, website) to resolve the ambiguity. |
| matches.business.<field>.status | enum | match, partial, no_match, not_searched, missing, invalid |
| matches.business.<field>.score | number | null | Field score 0-100 (null if missing or not_searched) |
| matches.executives | array | Always present (empty if no executives in input). Order preserved = input order. |
| matches.executives[N].<field> | object | Status + score per field for each input executive (independent best-match, same enum as business) |
| timing_ms | number | Server-side processing time |
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'];
infoReal request / response pairs, frozen from the demo sandbox (100% fictitious data, no real identity). No API key required.
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
| Field | Sent (request) | On file (reference) | Status |
|---|
No comparison: the request is rejected before any lookup (400).
Consumption reporting: request count + breakdown by billable product + pre-tax total.
| Header | Value | Required | Description |
|---|---|---|---|
| X-API-Key | vkey-... | yes | Account authentication key (otherwise HTTP 401 missing_api_key) |
| Param | Type | Required | Description |
|---|---|---|---|
| start | string | no | Start date YYYY-MM-DD (default: first day of current month) |
| stop | string | no | End date YYYY-MM-DD (default: last day of current month) |
| Field | Type | Description |
|---|---|---|
| period | object | { start, stop } - period actually queried |
| requests | number | Total number of requests |
| succeeded | number | Requests with at least one billable product |
| no_match | number | Verifications that ran but matched no billable product |
| errored | number | 4xx error requests (not billed) |
| failed | number | Legacy: no_match + errored (unbilled requests) |
| errors_complete | object | { code: count } of full 4xx errors (global rejects) |
| errors_partial | object | { code: count } of per-field invalid_* codes (200 responses, not exclusive of states) |
| total_pretax | number | Pre-tax total EUR for the period |
| currency | string | Currency code (EUR) |
| products[] | array | Breakdown by product (key, label, count, cpm_pretax, total_pretax) |
| timing_ms | number | Server processing time in milliseconds |
curl 'https://verify.zecible.fr/api/usage?start=2026-05-01&stop=2026-05-31' \ -H 'X-API-Key: vkey-...'
Global counter per API key, fixed 1-minute window, shared across /api/person, /api/business and /api/usage. Default limit: 60 requests per minute. Overridable per account (contact us for enterprise needs).
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum quota for the current window (e.g. 60). 0 = account with no limit. |
| X-RateLimit-Remaining | Remaining quota after this request. -1 if the account has no limit. |
| X-RateLimit-Reset | Unix UTC timestamp of the next reset (start of next minute). 0 if account has no limit. |
| Retry-After | Present only on HTTP 429. Number of seconds until the window resets. |
| Field | Description |
|---|---|
| error | "rate_limit_exceeded" |
| message | Human-readable message with the number of seconds until the next reset. |
| limit | Account quota. |
| reset_unix | Unix UTC timestamp of the next reset. |
X-RateLimit-Remaining on every response to anticipate throttling client-side.Retry-After before retrying (exponential backoff unnecessary: the window resets at the exact minute).# 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 }
Exhaustive list of error codes returned by the API. Uniform response format: {"error": "code_machine", "message": "human explanation"}. Always JSON, UTF-8 charset.
| Code | Endpoint(s) | Cause |
|---|---|---|
| missing_api_key | all | Header X-API-Key absent or empty. |
| invalid_api_key | all | Unknown or revoked key. Check the exact value (case-sensitive) and that it belongs to an active account. |
| account_disabled | all | Account deleted or disabled on the Zecible side. Contact support for reactivation. |
| ip_not_allowed | all | Calling IP not in the account whitelist. The message contains the rejected IP. Add the IP via support. |
| Code | Endpoint(s) | Cause |
|---|---|---|
| method_not_allowed | all | POST expected on /api/person and /api/business. GET expected on /api/usage. |
| Code | Endpoint(s) | Cause |
|---|---|---|
| invalid_body | person, business | Body absent, malformed JSON, or non-object root. |
| invalid_reference | person, business | The reference field must be a string (passthrough) of at most 256 characters. |
| missing_required_fields | person | person.first_name and person.last_name are required. |
| missing_required_fields | business | At least one of business.siret, business.siren, or business.name + business.address.city OR business.address.postal_code. |
| insufficient_data | person | Identity provided but no discriminating criterion (birth_date, phone, email or full address) to identify a target. |
| insufficient_data | business | No usable combination (SIRET / SIREN / name + address / name + contact). |
| too_many_executives | business | executives[] contains more than 10 entries. Limit the request to 10 executives or chain multiple requests. |
| invalid_match_rule | person | The match_rule field is malformed (nested {and|or:[...]} tree over known person fields expected). |
| invalid_min_score | person | The min_score field must be an integer between 1 and 100. |
| invalid_name_threshold | person/business | The name_threshold field must be a number between 0.5 and 1.0. |
These codes are not HTTP errors: the request returns 200. The invalid field is ignored (excluded from the search) and reported in matches.<field>.status = invalid, with the code echoed in error. Edge case: if an invalid siret/siren leaves no usable identifier, the request is instead rejected with missing_required_fields / insufficient_data (400).
| Code | Endpoint(s) | Cause |
|---|---|---|
| invalid_birth_date | person | person.birth_date is not a valid date (YYYY-MM-DD recommended). |
| invalid_mobile | person, business | The mobile field must be a FR mobile number (10 digits starting with 06 or 07). |
| invalid_landline | person, business | The landline field must be a FR landline number (not a mobile). |
| invalid_phone | person, business | The phone field must be a valid FR phone number (10 digits). |
| invalid_email | person, business | The email field must be a valid email address. |
| invalid_gender | person | The gender field must be M or F. |
| invalid_siret | business | The siret field must be a valid SIRET (14 digits, Luhn checksum). |
| invalid_siren | business | The siren field must be a valid SIREN (9 digits, Luhn checksum). |
| Code | Endpoint(s) | Cause |
|---|---|---|
| body_too_large | person, business | Request body exceeds 32 KB. Reduce the JSON size (e.g. limit the number of executives, shorten text fields). |
| Code | Endpoint(s) | Cause |
|---|---|---|
| rate_limit_exceeded | all | Per-minute quota reached. See the Rate limiting section for retry strategy and X-RateLimit-* headers. |
| Code | Endpoint(s) | Cause |
|---|---|---|
| quota_exceeded | person, business | Period credit cap reached (monthly budget or exhaustible account wallet). Headers X-Credit-Limit / X-Credit-Used / X-Credit-Remaining (EUR pre-tax) indicate the most constraining budget. Contact support to raise the cap. |
# 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)." }
The error field is stable and intended for programmatic handling. The message field is intended for display and may change between versions.
Stored only in your browser session (sessionStorage). Never sent outside API calls.