Contacts
10 endpoints
/v1/contactsLister les contacts
Retourne la liste paginée des contacts du client (cursor v2).
Tri stable created_at DESC, id DESC. Supporte recherche
case-insensitive sur email, first_name, last_name, company,
filtre par tag, et inclusion conditionnelle des définitions de
custom fields dans meta.
Fiches fusionnées
Une fiche absorbée par une fusion n'apparaît plus dans la liste : son historique vit dans la fiche conservée, seule à être listée.
Recherche
Le paramètre ?search est découpé en mots (séparés par des espaces).
Chaque mot doit apparaître, sans tenir compte de la casse, dans au
moins une des colonnes email, first_name, last_name ou company,
et les mots se combinent en ET, dans n'importe quel ordre :
?search=Jean Bouchard trouve le contact dont le prénom est « Jean »
et le nom « Bouchard ». Chaque mot est transmis comme une valeur
littérale entre guillemets : virgules, parenthèses et points n'altèrent
pas le filtre.
Inclusion custom fields
Passer ?include=custom_field_definitions ajoute le champ
meta.custom_field_definitions à la réponse — utile pour rendre
un formulaire d'édition côté intégrateur sans 2e round-trip.
contacts:readParamètres de requête
| Nom | Type | Obligatoire | Description |
|---|---|---|---|
search | string | optionnel | Recherche par mots dans email, first_name, last_name, company (insensible à la casse ; chaque mot doit correspondre à une colonne, mots combinés en ET). |
tag | string | optionnel | UUID d'un tag : ne retourne que les contacts qui portent ce tag,
paginés comme la liste complète. Pour
compter les leads d'une campagne, passer l'id de son tag
« Campagne : <nom> » (system_key qui commence par campaign:
dans GET /v1/tags) et parcourir les pages (next_cursor)
jusqu'à has_more: false : pagination.total_count n'est qu'une
estimation. Une valeur qui n'est pas un UUID est
refusée (400 invalid_request, details.field = "tag"). |
include | string | optionnel | Inclut le bloc meta.custom_field_definitions dans la réponse. |
Responses
| Statut | Description |
|---|---|
200 | Liste paginée des contacts. |
400 | La requête est mal formée (header manquant, JSON invalide, paramètre invalide). |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X GET 'https://api.capturia.io/v1/contacts' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json'/v1/contactsCréer un contact
Crée un contact sous le client de la clé, par le même chemin que le tableau de bord. Un courriel ou un téléphone est obligatoire (l'un des deux suffit). Le courriel est enregistré en minuscules ; le téléphone est normalisé en E.164 quand c'est un numéro nord-américain valide.
La personne est cherchée parmi toutes les identités du compte
(courriels et téléphones principaux et secondaires, sans égard à la
casse). Si elle existe déjà, rien n'est créé : la réponse est
409 duplicate_contact, avec la fiche existante dans data et les
champs concernés dans details.conflicting_fields. Si le courriel et
le téléphone appartiennent à deux fiches différentes, la réponse est
409 duplicate_resource, sans fiche.
Source et consentement
Le contact créé porte la source api. Une intégration ne nomme
personne qui aurait rencontré ce contact : aucun consentement
implicite (texto ou courriel) n'est inscrit à sa création. Les
relances par texto et les courriels de prospection de Capturia ne lui
partent qu'une fois qu'une vraie base de consentement existe
(formulaire, réservation, message entrant, attestation dans
Capturia). Pour capturer un lead qui vient de remplir ton formulaire,
avec son consentement, utilise POST /v1/leads/capture.
Idempotency
Le header Idempotency-Key est optionnel mais recommandé.
Une 2e requête avec la même clé et le même payload renvoie la
réponse d'origine sans dupliquer l'opération. Une 2e requête avec
la même clé et un payload différent retourne 409 idempotency_conflict.
Custom fields
Les clés de custom_fields sont le nom du champ tel que défini
dans Capturia (ou, de façon équivalente, l'id de sa définition) —
même contrat que /v1/events et /v1/leads/capture. Les valeurs
sont validées contre les définitions du client
(client_custom_field_definitions). Un champ inconnu ou un type
incompatible retourne 400 invalid_custom_fields avec details
listant les violations.
contacts:writeCorps de requête
| Propriété | Type | Obligatoire | Description |
|---|---|---|---|
email | string | optionnel | Courriel du contact, enregistré en minuscules. Une majuscule ne crée jamais une deuxième fiche. |
first_name | string | optionnel | — |
last_name | string | optionnel | — |
phone | string | optionnel | Téléphone du contact (50 caractères au plus) : un numéro joignable,
normalisable au format E.164 (ex +15145551234). Un numéro trop
court, incomplet ou suivi d'un poste est refusé (400,
validation_error, raison invalid_phone sur le champ phone),
comme à la modification d'un contact. |
company | string | optionnel | — |
deal_value | number | optionnel | Valeur estimée de l'opportunité en CAD. |
custom_fields | object | null | optionnel | Champs personnalisés, keyés par le nom du champ (ou l'id de sa
définition). Validés contre client_custom_field_definitions à
l'écriture — un champ inconnu, un type incompatible ou une valeur
trop longue retourne 400 invalid_custom_fields, sous la clé
envoyée. Longueurs maximales : texte 1 000 caractères, texte long
4 000, URL 2 048 (http(s)://), courriel 320, téléphone 30 ; une
liste n'accepte que l'une de ses options. En lecture, les clés
retournées sont les ids de définition (forme de stockage). |
system_id | string | optionnel | Système cible du contact créé (UUID d'un Système du compte). Le contact
est engagé dans ce Système. Absent → Système principal (comportement
historique). UUID inconnu pour ce compte → erreur system_not_found
(422), aucun contact créé ; Système archivé → repli silencieux sur le
Système principal. |
Responses
| Statut | Description |
|---|---|
201 | Contact créé. |
400 | Validation des paramètres ou du body a échoué. |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
409 | Une ressource avec un identifiant unique conflictuel existe déjà. |
415 | Le header |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X POST 'https://api.capturia.io/v1/contacts' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json' \
-d '{
"email": "[email protected]",
"first_name": "Alex",
"last_name": "Tremblay",
"phone": "+15145551234",
"company": "Acme Inc.",
"deal_value": 2500
}'/v1/contacts/{id}Récupérer un contact
Retourne un contact par son ID. Le paramètre ?expand=tags joint la
table client_contact_tags pour inclure les tags rattachés (id, nom,
couleur, system_key) sans round-trip supplémentaire.
L'ID d'une fiche absorbée par une fusion répond 404 : lire, modifier
ou supprimer passe par la fiche conservée. Si la fusion est annulée
depuis le tableau de bord, la fiche redevient accessible sous son ID.
contacts:readParamètres de requête
| Nom | Type | Obligatoire | Description |
|---|---|---|---|
expand | string | optionnel | Si tags, ajoute client_contact_tags au contact retourné. |
Responses
| Statut | Description |
|---|---|
200 | Contact trouvé. |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
404 | La ressource demandée n'existe pas (ou pas dans le scope du tenant). |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X GET 'https://api.capturia.io/v1/contacts/{id}' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json'/v1/contacts/{id}Mettre à jour un contact
Met à jour les champs fournis du contact (champs absents conservés).
Au moins un champ doit être fourni sinon 400 no_fields.
Le champ custom_fields est mergé avec les valeurs existantes
(les clés non fournies sont conservées). Les clés sont le nom du
champ (ou l'id de sa définition) ; une clé inconnue retourne
400 invalid_custom_fields. Passer null efface tous les champs
personnalisés.
Le téléphone et le courriel sont uniques par compte : une valeur déjà
portée par une autre fiche (comme coordonnée principale ou
secondaire) retourne 409 duplicate_resource
(details.conflicting_field nomme le champ) et le contact n'est pas
modifié. Le téléphone est comparé une fois normalisé au format E.164.
La mise à jour passe par le même service que la fiche du tableau de
bord : mêmes validations (courriel, téléphone, montants positifs,
valeur de chaque champ personnalisé selon son type), même trace dans
la chronologie et le journal d'activité du compte, et le même
webhook lead_updated. Une valeur invalide retourne
400 validation_error (details[].field nomme le champ) ; un
téléphone qui ne peut pas se composer (qui ne se normalise pas au
format E.164) est refusé avec details[].reason = invalid_phone.
Un contact garde toujours au moins un prénom, un nom, un courriel ou
un téléphone : une requête qui viderait le dernier retourne
400 validation_error (details[].reason = identity_required), et
le contact n'est pas modifié.
La valeur (deal_value) d'un contact archivé ne se modifie pas :
422 business_rule_violation (details.reason =
contact_archived), et aucun champ de la requête n'est écrit.
Restaurer le contact depuis le tableau de bord d'abord.
Le champ notes est le texte libre de la fiche, remplacé à chaque
écriture. Pour ajouter une note à la chronologie du contact, utiliser
POST /v1/contacts/{id}/notes.
contacts:writeCorps de requête
| Propriété | Type | Obligatoire | Description |
|---|---|---|---|
email | string | optionnel | — |
first_name | string | null | optionnel | — |
last_name | string | null | optionnel | — |
phone | string | null | optionnel | Téléphone E.164 (ex +15145551234). |
company | string | null | optionnel | — |
deal_value | number | null | optionnel | — |
is_priority | boolean | optionnel | Marqué prioritaire pour traitement humain accéléré. |
notes | string | null | optionnel | Notes internes libres sur le contact. |
custom_fields | object | null | optionnel | Patch additif sur les champs personnalisés existants (les clés non
fournies sont conservées). Passer null efface tous les champs. |
Responses
| Statut | Description |
|---|---|
200 | Contact mis à jour. |
400 | Validation des paramètres ou du body a échoué. |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
404 | La ressource demandée n'existe pas (ou pas dans le scope du tenant). |
409 | Une ressource avec un identifiant unique conflictuel existe déjà. |
422 | La requête est syntaxiquement valide mais viole une règle métier. |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X PATCH 'https://api.capturia.io/v1/contacts/{id}' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json' \
-d '{
"first_name": "Alexandre",
"deal_value": 5000,
"is_priority": true
}'/v1/contacts/{id}Supprimer un contact
Supprime définitivement le contact, exactement comme depuis le tableau de bord : sa conversation d'inbox, ses échanges (SMS, courriels, transcriptions de chat), ses rendez-vous et ses fichiers partent avec lui. Ses messages vocaux et médias reçus par texto partent aussi, tout comme l'audio de ses appels et de ses rencontres et la transcription mot à mot de ses appels, y compris chez notre fournisseur téléphonique : ce retrait est durable et repris automatiquement si le fournisseur est momentanément injoignable. Le journal de ses appels (résumé, notes, issue) reste, détaché de la fiche, tout comme les ventes, commandes et paiements.
Le retrait hors plateforme (rendez-vous au calendrier Google connecté,
fichiers stockés) est fait dans la foulée, en best-effort : la
suppression du contact en base réussit même si un service externe est
momentanément injoignable. L'objet cleanup de la réponse l'indique —
failed > 0 signifie que des rendez-vous Google n'ont pas pu être
retirés du calendrier (compte déconnecté, panne) et y restent.
Exige le scope contacts:delete, distinct de l'écriture : comme dans
le tableau de bord, supprimer une fiche est une permission à part.
Seule une personne qui peut supprimer des prospects dans Capturia
peut l'accorder à une clé.
contacts:deleteResponses
| Statut | Description |
|---|---|
200 | Contact supprimé. |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
404 | La ressource demandée n'existe pas (ou pas dans le scope du tenant). |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X DELETE 'https://api.capturia.io/v1/contacts/{id}' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json'/v1/contacts/{id}/timelineTimeline d'un contact (activités chronologiques)
Retourne la timeline d'activités du contact (changements de stage, envois SMS/email, RDV, etc.) en ordre chronologique inverse.
Limitation actuelle
L'implémentation est un stub : la table pipeline_history référence
l'autre table contacts interne (et non client_contacts), donc
la liste retournée est toujours vide aujourd'hui. La pagination
est validée mais non appliquée. À implémenter dans une phase
ultérieure quand l'historique sera unifié sur client_contacts.
contacts:readResponses
| Statut | Description |
|---|---|
200 | Liste vide (implémentation stub) avec pagination valide. |
400 | La requête est mal formée (header manquant, JSON invalide, paramètre invalide). |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
404 | La ressource demandée n'existe pas (ou pas dans le scope du tenant). |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X GET 'https://api.capturia.io/v1/contacts/{id}/timeline' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json'/v1/contacts/{id}/tagsAttacher un ou plusieurs tags à un contact
Attache un ou plusieurs tags au contact via la table de jointure
client_contact_tags. Les tags doivent appartenir au même client
que le contact — sinon 400 validation_error avec la liste des
tags non trouvés. Un identifiant qui n'est pas un UUID retourne
aussi 400 validation_error.
L'opération est idempotente côté DB : appeler 2 fois avec les
mêmes tag_ids ne crée pas de doublons (upsert
ON CONFLICT (contact_id, tag_id) DO NOTHING).
Tags de campagne
Un tag dont la system_key commence par campaign: est géré par
Capturia : il suit la campagne du lead. Déjà porté par le contact, il
est ignoré (renvoyer la liste des tags d'un lead reste idempotent).
Absent du contact, il retourne 409 tag_managed_by_capturia et
aucun tag de la requête n'est attaché.
Lister les tags d'un contact
Il n'existe pas de GET dédié — utiliser
GET /v1/contacts/{id}?expand=tags pour récupérer les tags
actuellement rattachés.
contacts:writeCorps de requête
| Propriété | Type | Obligatoire | Description |
|---|---|---|---|
tag_ids | string[] | obligatoire | UUIDs des tags à attacher (tous doivent appartenir au même client, 100 au plus par requête). |
Responses
| Statut | Description |
|---|---|
200 | Tags attachés. Retourne la liste à jour des tags du contact. |
400 | Validation des paramètres ou du body a échoué. |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
404 | La ressource demandée n'existe pas (ou pas dans le scope du tenant). |
409 | Le tag visé est un tag de campagne géré par Capturia ( |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X POST 'https://api.capturia.io/v1/contacts/{id}/tags' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json' \
-d '{
"tag_ids": [
"8f3b1a2c-9d4e-4f5a-b6c7-d8e9f0a1b2c3",
"9a4c2b3d-0e5f-5a6b-c7d8-e9f0a1b2c3d4"
]
}'/v1/contacts/{id}/tags/{tagId}Détacher un tag d'un contact
Retire l'association entre un contact et un tag. Le tag lui-même n'est pas supprimé — il reste disponible pour d'autres contacts.
Si le tag n'est pas attaché au contact (ou si le contact appartient
à un autre client), retourne 404 resource_not_found.
Un tag de campagne (system_key qui commence par campaign:) suit
la campagne du lead : le détacher retourne
409 tag_managed_by_capturia.
contacts:writeResponses
| Statut | Description |
|---|---|
200 | Tag détaché. |
400 | Validation des paramètres ou du body a échoué. |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
404 | La ressource demandée n'existe pas (ou pas dans le scope du tenant). |
409 | Le tag visé est un tag de campagne géré par Capturia ( |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X DELETE 'https://api.capturia.io/v1/contacts/{id}/tags/{tagId}' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json'/v1/contacts/{id}/notesAjouter une note à la chronologie d'un contact
Ajoute une note à la chronologie du contact, exactement comme depuis la fiche du tableau de bord : elle s'ajoute aux notes précédentes, apparaît dans l'onglet Notes de la fiche et nourrit la qualification du lead. La note est signée de la clé d'API qui l'a écrite.
À ne pas confondre avec le champ texte libre notes du contact
(PATCH /v1/contacts/{id}), remplacé à chaque écriture.
Le contenu est obligatoire et fait au plus 20 000 caractères ; sinon
400 validation_error. Un contact inconnu, d'un autre compte ou
fusionné dans une autre fiche retourne 404.
Idempotency
Le header Idempotency-Key est optionnel mais recommandé : une
2e requête avec la même clé, le même contact et le même contenu
renvoie la note d'origine sans en écrire une deuxième. La même clé
avec un autre contact ou un autre contenu retourne
409 idempotency_conflict.
contacts:writeCorps de requête
| Propriété | Type | Obligatoire | Description |
|---|---|---|---|
content | string | obligatoire | Texte de la note. |
Responses
| Statut | Description |
|---|---|
201 | Note ajoutée à la chronologie du contact. |
400 | Validation des paramètres ou du body a échoué. |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
404 | La ressource demandée n'existe pas (ou pas dans le scope du tenant). |
409 | Une requête précédente avec la même |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X POST 'https://api.capturia.io/v1/contacts/{id}/notes' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json' \
-d '{
"content": "Appel de suivi : le client veut une soumission pour 3 succursales."
}'/v1/contacts/batchCréer ou mettre à jour plusieurs contacts en une requête
Upsert atomique de jusqu'à 100 contacts en une seule requête. Les contacts dont l'email existe déjà sous le client sont mis à jour (les autres champs fournis remplacent les valeurs existantes), les autres sont insérés.
Déduplication interne
Le payload est dédupliqué en interne par lower(email) (last-entry
wins) avant l'upsert. L'ordre des contacts dans le tableau et la
casse de l'email n'affectent pas le hash idempotency.
Idempotency
Le header Idempotency-Key est optionnel mais recommandé. Le
hash idempotency porte sur le payload après dédup interne —
retry réseau idempotent même si le client renvoie les contacts dans
un ordre différent ou avec une casse email différente.
Réponse
L'endpoint retourne 200 avec {data: {processed, contacts: []}}
— pas de multi-status par contact. Une erreur de validation sur un
seul contact rejette l'ensemble du batch (400 validation_error
avec details listant chaque ligne fautive). Une erreur DB en
cours d'upsert retourne 500 — les contacts déjà persistés
avant l'erreur restent (pas de rollback transactionnel
cross-statements).
Quand system_id est fourni et résolu vers un Système actif, la
réponse inclut aussi system_routing : le bilan du routage des
contacts créés vers ce Système. Le routage est best-effort
(l'upsert est déjà commité quand il tourne) — refused + errors =
contacts créés restés dans le Système principal.
contacts:writeCorps de requête
| Propriété | Type | Obligatoire | Description |
|---|---|---|---|
contacts | array | obligatoire | Liste de contacts à upserter (max 100 par requête, sinon 400 batch_too_large). |
system_id | string | optionnel | Système cible du lot (UUID d'un Système du compte). Les
contacts créés par ce batch sont engagés dans ce
Système ; les contacts mis à jour gardent leur engagement.
Absent → Système principal (comportement historique). UUID
inconnu pour ce compte → system_not_found (422), aucun
contact écrit ; Système archivé → repli silencieux sur le
Système principal. |
trigger_automations | boolean | optionnel | Déclenche les automatisations et séquences actives du
compte pour les contacts de ce lot (contact créé, tags
ajoutés). Défaut false : rien ne se déclenche —
aucun enrôlement d'automatisation ni de séquence, aucun
courriel ni SMS causé par cet import. À true, la réponse
remonte automations_enrolled et, si le plafond horaire
du moteur (200 enrôlements/compte/heure) est atteint,
automations_skipped_rate_limit — jamais d'abandon
silencieux. |
Responses
| Statut | Description |
|---|---|
200 | Batch traité. |
400 | Validation des paramètres ou du body a échoué. |
401 | Clé API inconnue ou format invalide. |
403 | Refus d'autorisation. Deux causes, distinguées par |
409 | Une requête précédente avec la même |
415 | Le header |
429 | Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint. |
500 | Erreur serveur inattendue. Toujours fournir le |
Exemple de requête
curl -X POST 'https://api.capturia.io/v1/contacts/batch' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json' \
-d '{
"contacts": [
{
"email": "[email protected]",
"first_name": "Alex",
"last_name": "Tremblay",
"phone": "+15145551234",
"company": "Acme Inc.",
"deal_value": 2500
},
{
"email": "[email protected]",
"first_name": "Morgan",
"last_name": "Bélanger",
"company": "Example Inc."
}
]
}'