Contacts
9 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.
Recherche
Le paramètre ?search est sanitizé côté serveur — les caractères
%, _, ,, (, ), ., *, \ sont retirés avant l'application
du filtre ilike pour empêcher l'injection PostgREST.
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:readQuery parameters
| Name | Type | required | Description |
|---|---|---|---|
search | string | optional | Recherche dans email, first_name, last_name, company (case-insensitive, sanitizée). |
tag | string | optional | UUID d'un tag — filtre les contacts qui ont ce tag. |
include | string | optional | Inclut le bloc `meta.custom_field_definitions` dans la réponse. |
Responses
| Status | 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 |
Example request
curl -X GET 'https://app.capturia.io/api/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é. Email obligatoire et
dédupliqué — si un contact avec ce mail existe déjà sous ce client,
la réponse est 409 duplicate_resource (le contact existant n'est
pas écrasé).
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:writeRequest body
#/components/schemas/ContactCreate
Responses
| Status | 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 |
Example request
curl -X POST 'https://app.capturia.io/api/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) sans round-trip supplémentaire.
contacts:readQuery parameters
| Name | Type | required | Description |
|---|---|---|---|
expand | string | optional | Si `tags`, ajoute `client_contact_tags` au contact retourné. |
Responses
| Status | 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 |
Example request
curl -X GET 'https://app.capturia.io/api/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.
contacts:writeRequest body
#/components/schemas/ContactUpdate
Responses
| Status | 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). |
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 |
Example request
curl -X PATCH 'https://app.capturia.io/api/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. Les ventes, commandes et paiements sont conservés, simplement détachés de la fiche.
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.
contacts:writeResponses
| Status | 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 |
Example request
curl -X DELETE 'https://app.capturia.io/api/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
| Status | 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 |
Example request
curl -X GET 'https://app.capturia.io/api/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.
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).
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:writeRequest body
| Property | Type | Required | Description |
|---|---|---|---|
tag_ids | string[] | required | UUIDs des tags à attacher (tous doivent appartenir au même client). |
Responses
| Status | 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). |
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 |
Example request
curl -X POST 'https://app.capturia.io/api/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.
contacts:writeResponses
| Status | Description |
|---|---|
200 | Tag détaché. |
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 |
Example request
curl -X DELETE 'https://app.capturia.io/api/v1/contacts/{id}/tags/{tagId}' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json'/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:writeRequest body
| Property | Type | Required | Description |
|---|---|---|---|
contacts | array | required | Liste de contacts à upserter (max 100 par requête, sinon `400 batch_too_large`). |
system_id | string | optional | 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 | optional | 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
| Status | 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 |
Example request
curl -X POST 'https://app.capturia.io/api/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."
}
]
}'