---
title: "Contacts"
description: "Gérer les contacts clients rattachés à un compte."
canonical: https://developer.billabex.com/fr/guides/contacts/
lang: fr
alternate: https://developer.billabex.com/en/guides/contacts/
last-updated: 2026-10-03
---

# Contacts

> Gérer les contacts clients rattachés à un compte.

Source: https://developer.billabex.com/fr/guides/contacts/
Langue: Français (fr)
Version anglaise: https://developer.billabex.com/en/guides/contacts/

Les contacts sont les personnes de **l'organisation cliente** rattachées à un compte Billabex. Ce sont eux qui reçoivent les relances de paiement et les autres communications de l'agent.

## Vue d'ensemble

Quand l'agent Billabex envoie une relance, il doit savoir **qui contacter** chez le client. Les contacts portent cette information.

## Propriétés d'un contact

| Propriété  | Type   | Description                                       |
| ---------- | ------ | ------------------------------------------------- |
| `id`       | chaîne | Identifiant unique (UUID)                         |
| `fullName` | chaîne | Nom complet de la personne, ou `null`             |
| `email`    | objet  | Adresse email et statut de validation             |
| `phones`   | tableau | Tous les numéros de téléphone du contact         |
| `language` | chaîne | Code ISO 639-1 de la langue de communication      |
| `jobTitle` | chaîne | Fonction côté client, ou `null`                   |

### Statut de l'email

L'objet `email` contient l'adresse et son statut de validation :

```json
{
  "value": "john@acme.com",
  "status": "Valid",
  "isValid": true
}
```

Valeurs possibles :

| Statut       | Description                                   |
| ------------ | --------------------------------------------- |
| `Valid`      | L'email est délivrable                        |
| `Bounced`    | L'email a rebondi, il n'est pas délivrable     |
| `Complained` | Le destinataire a signalé les emails comme indésirables |

Le statut et `isValid` sont en lecture seule et mis à jour automatiquement d'après les résultats de livraison.

## Numéros de téléphone

Un contact peut porter **autant de numéros que nécessaire, dans n'importe quel pays**. Chaque entrée ressemble à ceci :

```json
{
  "number": "+33612345678",
  "type": "Mobile",
  "country": "FR",
  "origin": "Manual"
}
```

| Champ     | Description                                                                                      |
| --------- | ------------------------------------------------------------------------------------------------ |
| `number`  | Au format E.164, la forme canonique que Billabex stocke et la clé sur laquelle il déduplique      |
| `type`    | `Mobile`, `Landline` ou `Unknown`, **déduit du numéro** et en lecture seule                       |
| `country` | ISO 3166-1 alpha-2, déduit lui aussi, `null` quand le numéro n'appartient à aucun pays unique     |
| `origin`  | `Manual` pour ce que vous envoyez par l'API, `Connector` pour ce qu'une intégration a apporté     |

`type` et `country` sont calculés à partir des métadonnées d'opérateur, jamais de ce que vous
envoyez. `Unknown` est une vraie réponse et non un échec : plusieurs plans de numérotation, dont
celui d'Amérique du Nord, ne distinguent pas du tout les mobiles des fixes.

Les numéros analysables de longueur plausible sont acceptés, même si leur préfixe n'est pas
reconnu. Les numéros français à dix chiffres en `06`/`07` sont classés `Mobile` et peuvent être
envoyés au fournisseur SMS, qui décide de leur délivrabilité.

### Écrire des numéros

Envoyez `phones` sous forme d'une liste d'objets `{ number, countryCode? }` :

```json
{
  "phones": [
    { "number": "+33612345678" },
    { "number": "06 12 34 56 78", "countryCode": "FR" },
    { "number": "0475 12 34 56", "countryCode": "BE" }
  ]
}
```

Un numéro commençant par `+` ou `00` porte son propre indicatif et n'a besoin de rien d'autre.
**Tout autre numéro est un numéro national et exige `countryCode`** (ISO 3166-1 alpha-2). Il n'y a
pas de pays par défaut : en deviner un reviendrait à enregistrer en silence un autre numéro que
celui que vous vouliez.

La liste fait **autorité**. Omettez `phones` pour laisser les numéros du contact intacts, envoyez
`[]` pour les effacer.

### Numéros apportés par un connecteur

Si le compte est synchronisé avec un outil de facturation (Odoo, Pennylane, Qonto, Sellsy, Stripe,
Zoho Books), les numéros que cet outil porte apparaissent avec `origin: "Connector"`. Écrire
`phones` **ne les remplace jamais** : cela ne remplace que les entrées `Manual`, parce que la
synchronisation suivante écraserait de toute façon votre modification. Déliez le compte de sa source
pour reprendre la main sur ses numéros.

Un client détenant le scope `sync` écrit lui-même dans la pile `Connector` : ses numéros n'entrent
donc jamais en collision avec ce qu'une personne a saisi dans l'application. Voyez
[Scopes](/fr/guides/scopes/).

### Écritures partiellement appliquées : le tableau `warnings`

Créer ou mettre à jour un contact renvoie `200` même quand Billabex n'a gardé qu'une partie de ce
que vous avez envoyé. Dans ce cas, la réponse porte un tableau `warnings`, entièrement absent quand
rien n'a été écarté :

```json
"warnings": [
  { "field": "fullName", "value": "ACME Purchasing", "reason": "MANUALLY_SET" },
  { "field": "phones", "value": "voir Jean", "reason": "UNPARSEABLE" }
]
```

| `reason`       | Ce qui s'est passé                                                                                                   |
| -------------- | --------------------------------------------------------------------------------------------------------------------- |
| `MANUALLY_SET` | Une personne a saisi ce nom ou cette langue dans l'application, et un client `sync` ne l'écrase jamais                |
| `UNPARSEABLE`  | Le numéro n'a pas pu être lu comme un numéro : il a été écarté, et le reste du contact a bien été écrit              |

`UNPARSEABLE` n'apparaît que pour un client détenant le scope `sync`. Sans ce scope, un numéro
illisible donne un `400` sur toute la requête, parce qu'une personne qui saisit un numéro doit être
avertie qu'il est erroné plutôt que de le voir disparaître en silence.

### Joignabilité par SMS

Le canal SMS ne peut délivrer qu'à un mobile de France ou de Guyane : `type: "Mobile"` avec
`country: "FR"` ou `country: "GF"`. Tout autre numéro est stocké et renvoyé normalement, il ne peut
simplement pas recevoir de message. L'indicateur `hasValidPhoneContact` du compte vous dit si au
moins un contact actif est joignable de cette façon.

## Endpoints

| Méthode | Endpoint                                     | Description            |
| ------- | -------------------------------------------- | ---------------------- |
| GET     | `/accounts/{accountId}/contacts`             | Lister les contacts    |
| POST    | `/accounts/{accountId}/contacts`             | Créer un contact       |
| PATCH   | `/accounts/{accountId}/contacts/{contactId}` | Modifier un contact    |
| DELETE  | `/accounts/{accountId}/contacts/{contactId}` | Supprimer un contact   |
| PUT     | `/accounts/{accountId}/contacts/upsert`      | Créer ou mettre à jour |

## Créer un contact

```javascript
const response = await fetch(
  'https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/contacts',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      fullName: 'John Smith',
      email: 'john.smith@acme.com',
      language: 'fr',
      jobTitle: 'Responsable comptabilité',
      phones: [{ number: '+33612345678' }],
    }),
  },
);

const contact = await response.json();
```

### Avec cURL

```bash
curl -X POST "https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/contacts" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "fullName": "John Smith",
    "email": "john.smith@acme.com",
    "language": "fr",
    "phones": [{ "number": "06 12 34 56 78", "countryCode": "FR" }]
  }'
```

### Champs

| Champ      | Obligatoire     | Description                                                            |
| ---------- | --------------- | ---------------------------------------------------------------------- |
| `fullName` | l'un des deux   | Nom complet de la personne                                             |
| `email`    | l'un des deux   | Adresse email                                                          |
| `language` | non             | Code ISO 639-1 (`en`, `fr`, `de`…), voir la valeur par défaut plus bas |
| `phones`   | non             | Numéros de téléphone, voir plus haut                                   |
| `jobTitle` | non             | Fonction côté client, en texte libre                                   |

Un contact a besoin **d'un nom ou d'une adresse email**, pas des deux. Envoyez l'adresse seule quand
c'est tout ce dont vous disposez : aucun nom n'en est déduit, et `fullName` se relit à `null`, ce qui
vous permet de distinguer « pas de nom » d'un nom qui ressemblerait à une adresse. N'envoyer ni l'un
ni l'autre renvoie `400`.

`language` vaut par défaut la langue du premier contact actif du compte, puis la langue de contact
par défaut de l'organisation, puis `fr`.

## Modifier un contact

Utilisez PATCH pour ne mettre à jour que certains champs :

```javascript
const response = await fetch(
  'https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/contacts/CONTACT_ID',
  {
    method: 'PATCH',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      fullName: 'John A. Smith',
    }),
  },
);
```

Seuls les champs fournis sont modifiés. Pour `phones`, cela signifie : omettre le champ laisse les
numéros tels quels, envoyer la liste complète les remplace, envoyer `[]` les efface tous. Envoyer
`fullName: null` efface le nom, ce qui est refusé si le contact n'a plus d'adresse email pour
l'identifier.

## Lister les contacts

```javascript
const response = await fetch(
  'https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/contacts?first=10',
  {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  },
);

const data = await response.json();
// data.nodes contient le tableau des contacts
// data.pageInfo contient les informations de pagination
```

## Supprimer un contact

```javascript
const response = await fetch(
  'https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/contacts/CONTACT_ID',
  {
    method: 'DELETE',
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  },
);

// Renvoie 204 No Content en cas de succès
```

## Langue

Le champ `language` détermine la langue dans laquelle Billabex communique avec le contact. Utilisez les codes ISO 639-1 à deux lettres :

| Code | Langue     |
| ---- | ---------- |
| `en` | Anglais    |
| `fr` | Français   |
| `de` | Allemand   |
| `es` | Espagnol   |
| `it` | Italien    |
| `nl` | Néerlandais |
| `pt` | Portugais  |

L'agent traduit automatiquement ses relances dans la langue préférée du contact.

## Utiliser l'upsert pour synchroniser

Pour synchroniser des contacts depuis un système externe, l'upsert simplifie votre logique :

```javascript
// Synchroniser les contacts depuis votre CRM
for (const crmContact of crmContacts) {
  await fetch(
    `https://next.billabex.com/api/public/v1/accounts/${crmContact.billabexAccountId}/contacts/upsert`,
    {
      method: 'PUT',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        fullName: crmContact.name,
        email: crmContact.email,
        language: crmContact.preferredLanguage || 'fr',
        // Votre CRM sait à quel pays appartient chaque numéro ; Billabex ne le devinera pas.
        phones: crmContact.phoneNumbers.map((number) => ({
          number,
          countryCode: crmContact.countryCode,
        })),
      }),
    },
  );
}
```

Le guide des [opérations upsert](/fr/guides/upsert/) détaille la logique de correspondance.

## Bonnes pratiques

### Renseigner la bonne langue

Une langue correcte garantit que le client reçoit une communication qu'il comprend :

```javascript
// Faire correspondre la langue réellement préférée du contact
"language": "fr"  // Pour un contact francophone
```

### Traiter les rebonds d'email

Surveillez le champ `email.status` et mettez à jour vos contacts quand un email rebondit :

```javascript
if (contact.email?.status === 'Bounced') {
  // Alerter votre équipe pour mettre à jour l'adresse du contact
  // Ou marquer automatiquement le contact comme à traiter
}
```

## Trouver un contact quand personne n'est joignable

Un compte dont aucun contact n'a d'adresse email valide ne peut pas être relancé du tout. Le premier
endpoint cherche des contacts possibles pour **0,25 crédit par résultat**. Le second cherche leurs
coordonnées pour **1** crédit pour une adresse email, **2** dès qu'un numéro de téléphone est
trouvé, et **0** quand rien n'est trouvé.

### 1. Rechercher des candidats (facturé)

Renvoie jusqu'à dix personnes côté client qui semblent s'occuper du paiement des fournisseurs
(comptabilité, recouvrement, trésorerie, direction financière), avec leur fonction et l'URL de leur
profil public, et **aucune coordonnée**. L'appel dépense 0,25 crédit par résultat renvoyé et exige
le scope `accounts:all`. Il échoue avant d'appeler le prestataire si le portefeuille contient moins
de 1,25 crédit, soit le prix d'un résultat plus l'enrichissement le moins cher : une liste de noms
qu'on ne peut convertir en aucune adresse email ne vaut rien. Pour la même raison, l'appel ne
renvoie jamais plus de résultats que le portefeuille ne peut ensuite en enrichir : un petit solde
donne donc une liste courte plutôt que dix noms et un portefeuille vide.

```bash
curl -X POST "https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/contact-enrichment/candidates" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

```json
{
  "candidates": [
    {
      "fullName": "Marie Dupont",
      "jobTitle": "Responsable comptabilité",
      "companyName": "ACME SAS",
      "profileUrl": "https://www.linkedin.com/in/marie-dupont",
      "isLegalRepresentative": false,
      "officialRole": null
    }
  ],
  "creditsCharged": 0.25,
  "balanceAfter": 9.75,
  "searchScope": "Finance"
}
```

**`searchScope` vous dit qui vous avez sous les yeux.** Une entreprise de trois personnes n'emploie
personne dont la fonction est « Finance » : c'est son gérant qui paie les factures, et son titre dit
« Gérant ». Quand la recherche finance ne trouve personne, l'appel élargit la question de lui-même
et dit jusqu'où il est allé :

| `searchScope`    | Qui sont les candidats                                                               |
| ---------------- | ------------------------------------------------------------------------------------ |
| `Finance`        | Des profils finance, assez seniors pour agir sur un paiement. La réponse visée.       |
| `FinanceWide`    | Des profils finance de toute spécialité : le client n'étiquette rien précisément.     |
| `Decisionmakers` | Dirigeants et C-level de toute fonction : qui que ce soit qui dirige l'entreprise.    |

L'élargissement est gratuit et ne remplace jamais une vraie réponse : un seul profil finance
l'arrête. Traitez toute valeur autre que `Finance` comme le signal que vous vous apprêtez à
contacter un dirigeant plutôt qu'un comptable.

**`isLegalRepresentative`** est posé quand le registre public français désigne cette personne comme
dirigeant légal de l'entreprise, et `officialRole` porte alors sa qualité (« Président de SAS »,
« Gérant »). Ces candidats sont listés en premier. L'indicateur est informatif : il ne coûte rien,
ne change aucun prix, et une personne que le registre connaît mais que le prestataire ignore n'est
simplement pas renvoyée.

### 2. Enrichir un contact (facturé)

Renvoyez le `profileUrl` du candidat que vous avez retenu, ou le nom complet d'une personne que vous
connaissez déjà, sur un compte qui déclare un domaine d'entreprise. L'appel rend la main dès que le
prestataire l'a accepté ; le contact apparaît sur le compte **une minute plus tard environ, tout
seul**. Il n'y a rien à interroger en boucle, et fermer la connexion ne perd rien.

```bash
curl -X POST "https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/contact-enrichment" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "fullName": "Marie Dupont",
    "profileUrl": "https://www.linkedin.com/in/marie-dupont",
    "jobTitle": "Responsable comptabilité",
    "wantsPhone": true
  }'
```

| Champ        | Obligatoire | Description                                                                                              |
| ------------ | ----------- | -------------------------------------------------------------------------------------------------------- |
| `wantsPhone` | oui         | Chercher aussi un numéro : 2 crédits au lieu d'1, et seulement si un numéro est trouvé                   |
| `contactId`  | non         | Compléter un contact existant plutôt qu'en ajouter un                                                    |
| `fullName`   | non         | Qui chercher, prénom et nom. Obligatoire sauf si `contactId` ou `profileUrl` identifie la personne       |
| `profileUrl` | non         | Profil LinkedIn, issu de la recherche de candidats ou de vos propres données                             |
| `jobTitle`   | non         | Fonction de la personne, quand la recherche de candidats en a renvoyé une                                |

**La recherche doit être ancrée sur le débiteur.** L'appel exige soit un `profileUrl`, qui nomme une
personne, soit un `domain` d'entreprise sur le compte, qui nomme une entreprise. Sans l'un des deux,
il est refusé en 400 (`app-invoicing.contact.enrichment.not-anchored`) et **rien n'est dépensé** :
un nom seul correspond à une entreprise homonyme n'importe où dans le monde, le prestataire répond
avec son adresse, et rien dans cette réponse ne dit que ce n'est pas la bonne entreprise. Renseigner
le `domain` du compte est en général le correctif le moins cher, et cela donne aussi son logo au
compte. Il reste modifiable sur un compte synchronisé par un connecteur, contrairement aux autres
champs du compte, et une valeur saisie par une personne n'est jamais écrasée par une
synchronisation : un client détenant le scope `sync` qui tente de la remplacer reçoit
`warnings: [{ "field": "domain", "reason": "MANUALLY_SET" }]` dans la réponse de `PUT /accounts/{accountId}`
ou de l'outil MCP `update-account`.

**Un nom, c'est un prénom et un nom.** Le prestataire accepte soit les deux ensemble, soit une
`linkedin_url`. Un mot unique est refusé en 400
(`app-invoicing.contact.enrichment.identity-incomplete`) plutôt qu'envoyé pour revenir vide. Passez
un `profileUrl` quand vous ne détenez qu'un seul mot.

Une adresse professionnelle et une adresse personnelle sont cherchées à chaque appel, si bien qu'une
personne joignable uniquement chez elle est trouvée aussi. Le prix ne change pas : 1 crédit pour une
adresse, 2 dès qu'un numéro revient, et l'adresse professionnelle l'emporte quand les deux sont
trouvées.

`profileUrl` est l'entrée la plus forte qu'on puisse donner au prestataire, et elle n'a pas à venir
de la recherche de candidats : passez le profil LinkedIn que vous détenez déjà. Tout ce qui n'est
pas une adresse LinkedIn est refusé en 400, plutôt que dépensé sur une recherche qui ne pouvait pas
aboutir.

Les deux mêmes opérations facturées existent sur le serveur MCP sous les noms
`search-contact-candidates` et `enrich-contact`. Toutes deux exigent `mcp:write`, et le client doit
demander confirmation avant de dépenser des crédits.

`search-contact-candidates` exige en outre un `domain` d'entreprise sur le compte. Sans lui, l'appel
renvoie `app-invoicing.contact.candidate-search.not-anchored` avant de facturer quoi que ce soit.
Renseignez le vrai site du client par `PUT /accounts/{accountId}` (ou l'outil `update-account`), à partir d'une information fournie par
l'utilisateur ou par un document, jamais d'un domaine deviné à partir du nom de l'entreprise.

## Pour aller plus loin

- **[Opérations upsert](/fr/guides/upsert/)** : créer ou mettre à jour en une requête
- **[Sources de compte](/fr/guides/sources-de-compte/)** : relier un compte à un système externe
- **[Démarrage](/fr/guides/demarrage/)** : les bases de l'API
- **[Référence de l'API](/fr/reference-api/)** : documentation complète des endpoints

## Support

Une question sur la gestion des contacts ?
Écrivez-nous via le [formulaire de contact](https://www.billabex.com/fr/contactez-nous/).

---

Billabex developer portal. OpenAPI specification: https://developer.billabex.com/openapi.json.
Agent instructions: https://developer.billabex.com/llms.txt. Complete documentation: https://developer.billabex.com/llms-full.txt.
