Aller au contenu

Dédoublonnage

Chaque requête POST /leads lance une détection automatique des doublons avant l’insertion. Cela évite de polluer votre base avec des entrées en double venant de sources différentes.

Klozeo examine les clés ci-dessous dans l’ordre et s’arrête à la première qui trouve un lead existant. Chaque vérification reste à l’intérieur de votre compte.

Ordre Clé Correspond quand Résultat
1 email Même email, sans tenir compte de la casse ni des espaces autour Fusion dans le lead existant
2 siren Même SIREN. Un SIRET de 14 chiffres est d’abord ramené à son SIREN de 9 chiffres Fusion dans le lead existant
3 source_id Même source_id (espaces autour ignorés) Fusion dans le lead existant
4 phone + name Même téléphone, caractère pour caractère (seuls les espaces autour sont ignorés, donc +33 1 23 45 67 89 et 0123456789 ne correspondent pas), et un nom similaire à plus de 80 % Fusion dans le lead existant
5 name + city Même nom et même ville, sans tenir compte de la casse Crée un nouveau lead et renvoie potential_duplicate_id

La similarité des noms ignore la casse et les espaces autour, et compte les modifications d’un seul caractère (insertion, suppression, remplacement) nécessaires pour passer d’un nom à l’autre. Les noms correspondent quand ces modifications représentent moins de 20 % de la longueur du nom le plus long. Acme Corp et ACME Corp. correspondent (1 modification sur 10 caractères) ; Acme Corp et ACME Corporation ne correspondent pas (7 modifications sur 16).

  • Email, SIREN, source_id : dans un compte, au plus un lead porte un email donné (sans tenir compte de la casse), un SIREN ou un source_id, donc une correspondance n’est jamais ambiguë. Attribuer à l’un de ces champs une valeur déjà portée par un autre lead est refusé avec 409 Conflict.
  • Téléphone + nom : plusieurs leads peuvent partager un numéro de téléphone. Klozeo examine jusqu’à 5 leads portant ce numéro, sans ordre garanti, et fusionne dans le premier dont le nom correspond.
  • Nom + ville : plusieurs leads peuvent partager un nom et une ville. potential_duplicate_id en désigne un, sans ordre garanti.

Quand un doublon est trouvé aux étapes 1 à 4, le lead existant conserve son id et est mis à jour :

  • Chaque champ que vous envoyez avec une valeur remplace la valeur stockée. Les champs que vous omettez, ou que vous envoyez vides, restent inchangés.
  • Une fusion ne modifie jamais le name, le status, l’assignee ni les coordonnées (latitude, longitude), même si vous les envoyez. Modifiez-les avec PUT /leads/{id}.
  • Les champs verrouillés ne sont jamais écrits.
  • Les tags sont ajoutés aux tags existants, jamais remplacés : un tag comme do-not-contact survit à chaque import. Les doublons sont ignorés ; les tags existants gardent leur ordre. Pour remplacer toute la liste, envoyez un PUT /leads/{id} explicite.
  • Les attributes personnalisés fusionnent sur le name exact, sensible à la casse : un nouveau nom est ajouté, un nom existant du même type est mis à jour avec la valeur entrante (y compris false, 0 et null). Les attributs que vous n’envoyez pas restent inchangés. Un attribut dont le type diffère de celui stocké, ou dont le nom correspond à plusieurs attributs stockés, est ignoré et listé dans la réponse avec sa raison. Les attributs personnalisés ne peuvent pas être verrouillés.
  • Opposition au contact n’est jamais levée par une fusion.
  • last_interaction_at est toujours mis à jour, même si aucun champ n’a changé : chaque nouvel envoi compte comme une nouvelle interaction.

Une opposition au contact (do_not_contact: true) n’est jamais levée par une fusion : un import qui envoie "do_not_contact": false, ou aucune valeur, la laisse en place, avec sa date et son origine initiales. Un import qui envoie "do_not_contact": true ajoute l’opposition au lead existant. Seul un DELETE /leads/{id}/do-not-contact explicite (tableau de bord ou API REST, jamais MCP) la lève, et la piste d’audit enregistre qui l’a fait.

Verrouillez un champ que vous avez vérifié avec POST /leads/{id}/locks/{field}, par exemple POST /leads/{id}/locks/email. À partir de là, aucune fusion, aucun import ni enrichissement d’entreprise n’écrit ce champ ; la réponse de fusion le liste dans skipped_locked_fields. Vous pouvez toujours le modifier vous-même avec PUT /leads/{id}. Déverrouillez-le avec DELETE /leads/{id}/locks/{field}. Chaque verrouillage et déverrouillage est enregistré dans la piste d’audit du lead, avec la personne qui l’a fait. → Référence API

Dans le tableau de bord, ouvrez un lead : le panneau Verrous de champ liste les champs verrouillés, et une icône de verrou les signale dans la vue d’ensemble. Les propriétaires et les éditeurs choisissent les champs sous Choisir les champs verrouillés ; les viewers voient les verrous sans pouvoir les modifier.

{
"id": "cl_existing...",
"message": "Duplicate detected, existing lead updated",
"created_at": 1703520000,
"duplicate": true,
"attribute_changes": [
{ "name": "segment", "action": "added" },
{ "name": "budget", "action": "updated" },
{ "name": "active", "action": "ignored", "reason": "type_mismatch" }
],
"skipped_locked_fields": ["email"]
}

Le statut HTTP est 200 OK (et non 201) lors d’une fusion. id et created_at sont ceux du lead existant.

attribute_changes liste chaque attribut entrant dans l’ordre de la requête : added, updated (même quand la valeur ne change pas), ou ignored avec une reason valant type_mismatch ou ambiguous_name. Le champ est omis quand la requête n’envoie aucun attribut. Un élément fusionné de POST /leads/batch et les outils MCP create_lead et batch_create_leads renvoient la même liste. Si un attribut est invalide, toute la fusion est refusée et rien n’est écrit.

skipped_locked_fields liste les champs entrants qui n’ont pas été écrits parce qu’ils sont verrouillés. Le champ est omis quand aucun champ verrouillé n’a été envoyé. Une fusion peut renvoyer les deux listes.

Sur une correspondance nom + ville uniquement, un nouveau lead est créé (201 Created) et la réponse contient une indication :

{
"id": "cl_new...",
"message": "Lead created successfully",
"created_at": 1703520000,
"potential_duplicate_id": "cl_similar..."
}

Les deux leads sont conservés. Quand vous créez un lead depuis le tableau de bord, un avertissement Possible duplicate detected affiche l’ID du lead similaire. Klozeo ne les fusionne pas à votre place : comparez les deux leads, puis mettez à jour ou supprimez celui que vous ne voulez pas.

Champ Mis à jour quand
updated_at Une écriture définit au moins un champ du lead, même avec la valeur qu’il avait déjà
last_interaction_at Un envoi arrive, y compris une fusion qui ne change rien

Utilisez sort_by=last_interaction_at&sort_order=DESC pour trouver les leads avec l’activité la plus récente, indépendamment des changements de données.

Quand une requête dépasse le délai, vous ne savez pas si elle est arrivée. Envoyez l’en-tête Idempotency-Key : si vous renvoyez la même requête avec la même clé, Klozeo renvoie le premier résultat et n’écrit rien une seconde fois. La clé protège les nouvelles tentatives d’une seule requête. Elle ne compare pas les leads : les clés de détection ci-dessus décident, pour chaque nouvelle requête, entre création et fusion. Deux requêtes avec des clés différentes peuvent encore créer deux leads. → Retries and idempotency

Chaque création, fusion, mise à jour, suppression et changement de score est enregistré, tout comme chaque changement d’attribut personnalisé et de note, avec le membre qui l’a fait (pour une clé API, le propriétaire du compte, et la clé dans api_key_id). Dans le tableau de bord, l’historique Ne pas contacter d’un lead affiche le nom de la clé API pour les changements enregistrés avec api_key_id, même après sa révocation. Dans le tableau de bord, cet historique affiche l’e-mail du membre qui a agi à tous les membres de l’espace de travail, et un membre retiré apparaît comme un ancien membre. Dans le tableau de bord, l’onglet historique d’un lead montre ses 50 changements les plus récents, sans les mises à jour automatiques du score : voir Auteurs des notes et historique. Récupérez l’historique d’un lead :

GET /leads/{id}/changes?limit=50
{
"changes": [
{
"id": "uuid-...",
"lead_id": "cl_...",
"workspace_id": "workspace_id",
"user_id": "workspace_id",
"actor_id": "actor_user_id",
"api_key_id": "api_key_id",
"action": "merged",
"before": { "name": "Acme", "email": "[email protected]" },
"after": { "name": "Acme", "email": "[email protected]" },
"reason": "dedup:email",
"created_at": 1703520000
}
],
"count": 1
}
action Quand
created Nouveau lead inséré
merged Fusion par dédoublonnage dans un lead existant (reason vaut dedup:email, dedup:siren, dedup:source_id ou dedup:phone+name)
updated PUT /leads/{id} explicite (ou mise à jour en lot), ou un attribut personnalisé ajouté, modifié ou supprimé (reason vaut attribute:<nom>)
deleted Lead supprimé
assigned, unassigned L’assigné d’un lead a été défini ou retiré ; assignee contient l’email du nouvel assigné sur assigned, before et after sont vides
note.created, note.updated, note.deleted Une note ajoutée, modifiée ou supprimée ; before et after contiennent la note. La suppression d’un lead écrit une entrée note.deleted par note, avec reason à lead:deleted
scored Le score stocké a changé ; before et after ne contiennent que score. reason vaut score:refresh (après une écriture de lead ou d’attribut personnalisé), score:recalculate (POST /leads/{id}/score ou POST /leads/score/batch) ou score:rules (rescore automatique après un changement de règle de scoring, sans actor_id)

before / after sont des copies JSON du lead (de la note pour les entrées note.*). La limit par défaut est 50 (100 au maximum). L’historique d’un lead supprimé est conservé 30 jours. Un traitement quotidien efface ensuite before, after et assignee : les valeurs ont disparu sous 31 jours après la suppression. Chaque entrée garde l’action, la date, le membre ou la clé API qui a agi, channel, reason et changed_fields, le nom des champs modifiés. L’historique d’un lead non supprimé n’est pas effacé.

api_key_id est l’id que GET /api-keys donne à la clé API à l’origine d’un changement fait avec une clé, par l’API REST ou par le serveur MCP. Pour un tel changement, actor_id est le propriétaire du compte. Le champ api_key_id reste dans l’historique après la révocation de la clé ; les entrées écrites avant son ajout n’en ont pas. Un changement fait depuis le tableau de bord n’a pas d’api_key_id. Un changement fait par un assistant connecté en OAuth n’en a pas non plus, et son actor_id est le membre qui a connecté l’assistant.