> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eat-now.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Clients (CRM)

> Créer, modifier et synchroniser les clients du restaurant sans passer par une réservation.

Les endpoints clients donnent à un CRM, un outil marketing ou un programme de
fidélité un accès direct aux fiches clients du restaurant : il n’est pas
nécessaire de passer par une réservation pour en créer ou en modifier une. La
lecture demande `CUSTOMERS_READ` ; la création et la modification demandent
`CUSTOMERS_WRITE` et `CUSTOMERS_READ`, car chaque écriture renvoie la fiche
complète. Une fiche client est une donnée personnelle de bout en bout : il
n’existe pas de vue masquée, n’accordez ces scopes qu’aux intégrations
autorisées à la détenir.

Les clients appartiennent au restaurant de la clé. Un groupe de restaurants a
une fiche par restaurant.

## La ressource client

`GET /customers/{id}` renvoie les coordonnées, les `tags`, la `note` interne,
les `custom_fields`, l’état marketing (`newsletter_consent`,
`marketing_opt_outs`), la blacklist (`blacklisted`) et des `stats` calculées à
partir de ses réservations (nombre, no-shows, montant payé, jour de la dernière
réservation). Les `stats` sont en lecture seule.

Les champs personnalisés sont indexés par identifiant de champ.
`GET /customer-fields` liste les champs du restaurant avec leur type et, pour
une liste, les valeurs autorisées :

| Type | Valeur |
| - | - |
| `TEXT` | texte |
| `NUMBER` | nombre |
| `DATE` | `"YYYY-MM-DD"` |
| `BOOLEAN` | `true` / `false` |
| `SELECT` | une `value` d’option |
| `MULTI_SELECT` | un tableau de `value` d’options distinctes |

Un champ `NUMBER` peut être borné par `min` / `max`, un `MULTI_SELECT` par
`max_items`. Une écriture avec un champ inconnu ou une valeur qui ne convient
pas renvoie `422`, avec une entrée par champ fautif dans
`error.details.issues`.

## Créer un client

`POST /customers` cherche un client existant avant d’en créer un, dans cet
ordre : même `external_id`, même numéro de téléphone (les écritures nationale et
internationale d’un numéro se correspondent), même email (sans tenir compte de
la casse). `on_match` décide de
ce que fait une correspondance :

| `on_match` | Correspondance trouvée | HTTP / `result` |
| - | - | - |
| `return_existing` (par défaut) | renvoyée telle quelle, rien n’est écrit | `200` `matched` |
| `update` | les champs fournis y sont écrits | `200` `updated` |
| `create_new` | ignorée : un nouveau client est toujours créé | `201` `created` |

Sans correspondance, le client est créé : `201` `created`.

```bash theme={null}
curl -X POST 'https://app.eat-now.io/api/partner/v1/customers' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Jane Doe",
    "email": "jane@example.com",
    "phone_number": "+33612345678",
    "external_id": "crm-4821",
    "tags": ["VIP"],
    "newsletter_consent": true,
    "on_match": "update"
  }'
```

Rejouer un appel qui a abouti retrouve le client qu’il a créé, à condition que
l’appel porte un `external_id`, un téléphone ou un email. Ce n’est pas un
verrou : deux appels identiques envoyés en même temps peuvent créer chacun un
client, ne lancez donc pas vos nouvelles tentatives en parallèle.

`external_id` est votre identifiant du client : stockez-le, il est comparé en
premier et reste stable quand le client change de téléphone ou d’email. Il est
écrit à la création et avec `on_match: "update"` ; une simple correspondance
`return_existing` laisse la fiche intacte, `external_id` compris. Il est
distinct des identifiants que d’autres canaux gardent sur le client (Réserver
avec Google par exemple), que l’API n’affiche ni ne modifie.

Un client blacklisté est renvoyé comme les autres, avec `blacklisted: true`.

## Modifier un client

`PATCH /customers/{id}` ne change que les champs qu’il contient ; `null` vide un
champ. `tags` remplace toute la liste, `custom_fields` ne change que les champs
nommés. `newsletter_consent: false` retire le consentement. La fiche est
partagée par toutes les réservations du client.

## Garder une copie synchronisée

`GET /customers` liste les clients par `updated_at` puis `id`, avec une
pagination par curseur (`limit` jusqu’à 100, 50 par défaut) :

1. Premier passage : appelez sans `updated_since` et suivez
   `pagination.next_cursor` jusqu’à `has_more: false`.
2. Gardez le `updated_at` du dernier client traité.
3. Passages suivants : passez-le en `updated_since` et parcourez les pages.

`updated_since` est inclusif : le client à la frontière revient une fois,
dédoublonnez sur `id`. Repartez à chaque passage quelques minutes avant le
dernier `updated_at` gardé : une écriture validée tardivement peut porter un
horodatage légèrement plus ancien.

Les changements de `stats` ne modifient pas `updated_at` : relisez un client
quand vous avez besoin de chiffres à jour. Un nouvel opt-out marketing le
modifie.

Le flux contient les clients qui existent. Un client supprimé dans EatNow, ou
fusionné dans un autre par l’équipe, en disparaît : les
[webhooks](#suivre-les-changements-en-direct) `customer.deleted` et
`customer.merged` le signalent. Sans webhooks, listez régulièrement tous les
clients (sans `updated_since`) et supprimez les identifiants qui ne reviennent
plus. `GET /customers` filtre
aussi par `email`, `phone`, `external_id` et `tag`.

## Clients et réservations

Une réservation n’a pas besoin d’un appel client préalable. `POST /reservations`
accepte :

* `customer: { "id": "…" }`, pour réserver pour un client déjà connu ;
* les coordonnées du client directement, pour le retrouver ou le créer dans le
  même appel, rapproché par téléphone puis email (pas d’`external_id` ici).
  S’il est absent, `customer.on_match` suit le réglage du restaurant
  (rapprochement, sauf si le restaurant l’a désactivé). Deux différences avec
  `POST /customers` : `return_existing` peut encore enregistrer un
  consentement newsletter que le client vient de donner, et `update` ne vide
  jamais une coordonnée (un email ou un téléphone vide est ignoré).

Un client blacklisté fait refuser la réservation, qu’il soit lié par `id` ou
retrouvé à partir des coordonnées. Le client n’est résolu qu’une fois les
contrôles de la réservation passés : une réservation refusée ne crée ni ne
modifie de client.

`GET /reservations?customer_id=…` liste les réservations d’un client.

## Suivre les changements en direct

Abonnez un endpoint à `customer.created`, `customer.updated`,
`customer.merged` et `customer.deleted` (voir la
[référence webhooks](/fr/api-reference/webhooks)) pour être prévenu des
changements au fil de l’eau, suppressions et fusions comprises. Les événements
portent le même objet client que `GET /customers/{id}`. Gardez la
synchronisation incrémentale ci-dessus comme source de vérité : l’import CSV ne
publie aucun événement, et une livraison manquée est rattrapée au passage
suivant.
