API Console - Interactive documentation
POST /api/person

Verify an individual's identity against the FR individual records database. 0-100 score per field + aggregated global score.

database
Database+300M FR individual records (multi-source)
speed
LatencySynchronous real-time response, JSON format
warning
Ambiguous caseScore capped at 50 if homonyms found

Required headers

HeaderValueRequiredDescription
X-API-Keyvkey-...yesAccount authentication key (otherwise HTTP 401 missing_api_key)
Content-Typeapplication/jsonyesRequest body content type JSON. The body is parsed as JSON regardless of the value.

Body

FieldTypeRequiredDescription
referencestringnoClient identifier returned as-is
person.first_namestringyesFirst name
person.last_namestringyesLast name
person.genderM | FnoGender
person.birth_datestringno*Strict ISO 8601 format YYYY-MM-DD (otherwise HTTP 400 invalid_birth_date)
person.address.streetstringno*Street + number
person.address.citystringno*City
person.address.postal_codestringno*5 digits (auto zero-padding)
person.emailstringno*Email (format validation)
person.mobilestringno*Strict FR mobile (otherwise HTTP 400 invalid_mobile)
person.landlinestringno*Strict FR landline (otherwise HTTP 400 invalid_landline)
person.phonestringno*FR phone (auto-detect mobile/landline). Returns matches.phone.resolved_as = mobile | landline
match_ruleobjectnoOptional 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_scoreintegernoThreshold 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.

Response 200

FieldTypeDescription
transaction_idstringUnique transaction identifier
referencestring | nullReference provided by the client (passthrough)
scorenumber | nullGlobal 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.
ambiguousbooleantrue if several distinct profiles match the provided criteria (homonyms at the same name+address with divergent contacts). The client must provide more contacts (email, mobile, DOB) to resolve the ambiguity.
matches[field].statusenummatch, partial, no_match, not_searched, missing
matches[field].scorenumber | nullScore 0-100 for this field (null if missing or not_searched)
timing_msnumberServer-side processing time
ruleobjectPresent only if match_rule is provided: {passed, min_score} (rule verdict; does not alter matches or score).

Example

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'];
returned as-is in the responsetag
first name (accents accepted)person
last name (accents accepted)badge
M male / F femalewc
strict ISO 8601 (e.g. 1980-05-15)cake
street + numbersignpost
BAN normalised city labellocation_city
5 digits (zero-pad auto if < 5)markunread_mailbox
RFC 5321 format (single email)alternate_email
FR 10 digits - auto mobile/landline (returns resolved_as)phone_iphone
FR 10 digits strict mobile (06/07)smartphone
FR 10 digits strict landline (01-05, 09)call
JSON tree {and|or:[...]} over the fields
Threshold 1-100 (default 50)speed

infoFictional demo data (no real identity).

Response
Submit the form to see the JSON response.
POST /api/business BETA

Verify a FR business (KYB) against the B2B database: SIRET, company name, contacts, executives. 0-100 score per field.

database
Database+50M FR business records
speed
LatencySynchronous real-time response, JSON format
manage_accounts
ExecutivesUp to 10 per request, independent best-match
warning
Ambiguous caseScore capped at 50 if multiple SIRETs

Required headers

HeaderValueRequiredDescription
X-API-Keyvkey-...yesAccount authentication key (otherwise HTTP 401 missing_api_key)
Content-Typeapplication/jsonyesRequest body content type JSON. The body is parsed as JSON regardless of the value.

Body

FieldTypeRequiredDescription
referencestringnoClient identifier returned as-is
business.siretstringno*14 digits (establishment)
business.sirenstringno*9 digits (legal entity)
business.vat_intracomstringnoIntra-EU VAT number (derived from SIREN, verified by key)
business.namestringno*Company name
business.legal_formstringnoLegal form (SAS, SARL, EURL...)
business.nafstringnoNAF / APE code (e.g. 7311Z)
business.address.streetstringnoStreet + number
business.address.citystringnoCity
business.address.postal_codestringno5 digits
business.emailstringnoContact email
business.mobilestringnoStrict FR mobile (otherwise HTTP 400 invalid_mobile)
business.landlinestringnoStrict FR landline (otherwise HTTP 400 invalid_landline)
business.phonestringnoFR phone (auto-detect mobile/landline). Returns matches.business.phone.resolved_as = mobile | landline
business.websitestringnoWebsite URL
executives[N].first_namestringnoExecutive or contact first name
executives[N].last_namestringnoExecutive or contact last name
executives[N].rolestringnoRole (president, manager, director, etc.)

* 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.

Response 200

FieldTypeDescription
transaction_idstringUnique transaction identifier
referencestring | nullReference provided by the client (passthrough)
scorenumber | nullGlobal 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.
ambiguousbooleantrue 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>.statusenummatch, partial, no_match, not_searched, missing
matches.business.<field>.scorenumber | nullField score 0-100 (null if missing or not_searched)
matches.executivesarrayAlways present (empty if no executives in input). Order preserved = input order.
matches.executives[N].<field>objectStatus + score per field for each input executive (independent best-match, same enum as business)
timing_msnumberServer-side processing time

Example

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'];
returned as-is in the responsetag
14 digits (spaces tolerated)fingerprint
9 digits (legal entity)fingerprint
FR + 11 digits (computed from SIREN)payments
company name or trade name (fuzzy)storefront
SAS, SARL, EURL, SA, SCI...gavel
4 digits + 1 letter (e.g. 7311Z)category
e.g. 12 rue de la Paixsignpost
establishment citylocation_city
5 digitsmarkunread_mailbox
contact email (RFC 5321)alternate_email
FR 10 digits (auto mobile/landline)call
full URL (https:// included)language
FR 10 digits strict mobile (06/07)smartphone
FR 10 digits strict landline (01-05, 09)call
first name (accents OK)person
last name (accents OK)badge
president, manager, director (fuzzy)work

infoFictional demo data (no real identity).

Response
Submit the form to see the JSON response.
GET /api/usage

Consumption reporting: request count + breakdown by billable product + pre-tax total.

person
ScopeAPI user of the caller (current key)
date_range
Default periodCurrent month (1st - last day)
euro
CurrencyEUR pre-tax, actual pro-rata per product

Required headers

HeaderValueRequiredDescription
X-API-Keyvkey-...yesAccount authentication key (otherwise HTTP 401 missing_api_key)

Query params

ParamTypeRequiredDescription
startstringnoStart date YYYY-MM-DD (default: first day of current month)
stopstringnoEnd date YYYY-MM-DD (default: last day of current month)

Response 200

FieldTypeDescription
periodobject{ start, stop } - period actually queried
requestsnumberTotal number of requests
succeedednumberRequests with at least one billable product
failednumberRequests with no billable product
total_pretaxnumberPre-tax total EUR for the period
currencystringCurrency code (EUR)
products[]arrayBreakdown by product (key, label, count, cpm_pretax, total_pretax)

Example

curl 'https://verify.zecible.fr/api/usage?start=2026-05-01&stop=2026-05-31' \
  -H 'X-API-Key: vkey-...'
YYYY-MM-DD - default: first day of current monthevent_available
YYYY-MM-DD - default: last day of current monthevent_busy

infoFictional demo data (no real identity).

Response
Submit the form to see the JSON response.
429 Rate limiting

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).

Response headers (all responses)

HeaderDescription
X-RateLimit-LimitMaximum quota for the current window (e.g. 60). 0 = account with no limit.
X-RateLimit-RemainingRemaining quota after this request. -1 if the account has no limit.
X-RateLimit-ResetUnix UTC timestamp of the next reset (start of next minute). 0 if account has no limit.
Retry-AfterPresent only on HTTP 429. Number of seconds until the window resets.

HTTP 429 response (quota exceeded)

FieldDescription
error"rate_limit_exceeded"
messageHuman-readable message with the number of seconds until the next reset.
limitAccount quota.
reset_unixUnix UTC timestamp of the next reset.

Recommended strategy

  • Read X-RateLimit-Remaining on every response to anticipate throttling client-side.
  • On HTTP 429, respect Retry-After before retrying (exponential backoff unnecessary: the window resets at the exact minute).
  • For volumes > 60 req/min, request a higher quota: contact us.
  • The counter starts on the first request of each UTC minute, regardless of the endpoint called.

Example 429 response

# 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 Error codes

Exhaustive list of error codes returned by the API. Uniform response format: {"error": "code_machine", "message": "human explanation"}. Always JSON, UTF-8 charset.

Authentication (401)

CodeEndpoint(s)Cause
missing_api_keyallHeader X-API-Key absent or empty.
invalid_api_keyallUnknown or revoked key. Check the exact value (case-sensitive) and that it belongs to an active account.
account_disabledallAccount deleted or disabled on the Zecible side. Contact support for reactivation.
ip_not_allowedallCalling IP not in the account whitelist. The message contains the rejected IP. Add the IP via support.

HTTP method (405)

CodeEndpoint(s)Cause
method_not_allowedallPOST expected on /api/person and /api/business. GET expected on /api/usage.

Body validation (400)

CodeEndpoint(s)Cause
invalid_bodyperson, businessBody absent, malformed JSON, or non-object root.
invalid_referenceperson, businessThe reference field exceeds 256 characters. Shorten the client passthrough.
missing_required_fieldspersonperson.first_name and person.last_name are required.
missing_required_fieldsbusinessAt least one of business.siret, business.siren, or business.name + business.address.city OR business.address.postal_code.
insufficient_datapersonIdentity provided but no discriminating criterion (birth_date, phone, email or full address) to identify a target.
insufficient_databusinessNo usable combination (SIRET / SIREN / name + address / name + contact).
invalid_birth_datepersonperson.birth_date is not in strict ISO 8601 format YYYY-MM-DD.
invalid_mobileperson, businessThe mobile field must be a FR mobile number (10 digits starting with 06 or 07).
invalid_landlineperson, businessThe landline field must be a FR landline number (not a mobile).
invalid_phoneperson, businessThe phone field must be a valid FR phone number (10 digits).
invalid_emailperson, businessThe email field must be a valid email address.
invalid_genderpersonThe gender field must be M or F.
invalid_siretbusinessThe siret field must be a valid SIRET (14 digits, Luhn checksum).
invalid_sirenbusinessThe siren field must be a valid SIREN (9 digits, Luhn checksum).
too_many_executivesbusinessexecutives[] contains more than 10 entries. Limit the request to 10 executives or chain multiple requests.

Payload size (413)

CodeEndpoint(s)Cause
body_too_largeperson, businessRequest body exceeds 32 KB. Reduce the JSON size (e.g. limit the number of executives, shorten text fields).

Quotas (429)

CodeEndpoint(s)Cause
rate_limit_exceededallPer-minute quota reached. See the Rate limiting section for retry strategy and X-RateLimit-* headers.

Credits / budget (402)

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

Response format

# 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.