FR

Contacts

9 endpoints

get/v1/contacts

Lister 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.

Required scopes: contacts:read

Query parameters

NameTyperequiredDescription
searchstringoptionalRecherche dans email, first_name, last_name, company (case-insensitive, sanitizée).
tagstringoptionalUUID d'un tag — filtre les contacts qui ont ce tag.
includestringoptionalInclut le bloc `meta.custom_field_definitions` dans la réponse.

Responses

StatusDescription
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 code : insufficient_scope (la clé ne possède pas le scope requis — details.required_scope) ou feature_not_available (l'API publique ou le module visé n'est pas inclus dans le forfait du compte — details.feature nomme la fonctionnalité, ex. public_api, quotes). Un refus de forfait est définitif pour la clé : inutile de réessayer, le compte doit changer de forfait.

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 request_id au support.

Example request

curl -X GET 'https://app.capturia.io/api/v1/contacts' \
  -H 'Authorization: Bearer cap_live_<your-key>' \
  -H 'Content-Type: application/json'
post/v1/contacts

Cré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.

Required scopes: contacts:write

Request body

#/components/schemas/ContactCreate

Responses

StatusDescription
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 code : insufficient_scope (la clé ne possède pas le scope requis — details.required_scope) ou feature_not_available (l'API publique ou le module visé n'est pas inclus dans le forfait du compte — details.feature nomme la fonctionnalité, ex. public_api, quotes). Un refus de forfait est définitif pour la clé : inutile de réessayer, le compte doit changer de forfait.

409

Une ressource avec un identifiant unique conflictuel existe déjà.

415

Le header Content-Type est manquant ou non supporté pour cet endpoint.

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 request_id au support.

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
}'
get/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.

Required scopes: contacts:read

Query parameters

NameTyperequiredDescription
expandstringoptionalSi `tags`, ajoute `client_contact_tags` au contact retourné.

Responses

StatusDescription
200

Contact trouvé.

401

Clé API inconnue ou format invalide.

403

Refus d'autorisation. Deux causes, distinguées par code : insufficient_scope (la clé ne possède pas le scope requis — details.required_scope) ou feature_not_available (l'API publique ou le module visé n'est pas inclus dans le forfait du compte — details.feature nomme la fonctionnalité, ex. public_api, quotes). Un refus de forfait est définitif pour la clé : inutile de réessayer, le compte doit changer de forfait.

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 request_id au support.

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'
patch/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.

Required scopes: contacts:write

Request body

#/components/schemas/ContactUpdate

Responses

StatusDescription
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 code : insufficient_scope (la clé ne possède pas le scope requis — details.required_scope) ou feature_not_available (l'API publique ou le module visé n'est pas inclus dans le forfait du compte — details.feature nomme la fonctionnalité, ex. public_api, quotes). Un refus de forfait est définitif pour la clé : inutile de réessayer, le compte doit changer de forfait.

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 request_id au support.

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
}'
delete/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.

Required scopes: contacts:write

Responses

StatusDescription
200

Contact supprimé.

401

Clé API inconnue ou format invalide.

403

Refus d'autorisation. Deux causes, distinguées par code : insufficient_scope (la clé ne possède pas le scope requis — details.required_scope) ou feature_not_available (l'API publique ou le module visé n'est pas inclus dans le forfait du compte — details.feature nomme la fonctionnalité, ex. public_api, quotes). Un refus de forfait est définitif pour la clé : inutile de réessayer, le compte doit changer de forfait.

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 request_id au support.

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'
get/v1/contacts/{id}/timeline

Timeline 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.

Required scopes: contacts:read

Responses

StatusDescription
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 code : insufficient_scope (la clé ne possède pas le scope requis — details.required_scope) ou feature_not_available (l'API publique ou le module visé n'est pas inclus dans le forfait du compte — details.feature nomme la fonctionnalité, ex. public_api, quotes). Un refus de forfait est définitif pour la clé : inutile de réessayer, le compte doit changer de forfait.

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 request_id au support.

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'
post/v1/contacts/{id}/tags

Attacher 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.

Required scopes: contacts:write

Request body

PropertyTypeRequiredDescription
tag_idsstring[]requiredUUIDs des tags à attacher (tous doivent appartenir au même client).

Responses

StatusDescription
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 code : insufficient_scope (la clé ne possède pas le scope requis — details.required_scope) ou feature_not_available (l'API publique ou le module visé n'est pas inclus dans le forfait du compte — details.feature nomme la fonctionnalité, ex. public_api, quotes). Un refus de forfait est définitif pour la clé : inutile de réessayer, le compte doit changer de forfait.

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 request_id au support.

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"
  ]
}'
delete/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.

Required scopes: contacts:write

Responses

StatusDescription
200

Tag détaché.

401

Clé API inconnue ou format invalide.

403

Refus d'autorisation. Deux causes, distinguées par code : insufficient_scope (la clé ne possède pas le scope requis — details.required_scope) ou feature_not_available (l'API publique ou le module visé n'est pas inclus dans le forfait du compte — details.feature nomme la fonctionnalité, ex. public_api, quotes). Un refus de forfait est définitif pour la clé : inutile de réessayer, le compte doit changer de forfait.

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 request_id au support.

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'
post/v1/contacts/batch

Cré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.

Required scopes: contacts:write

Request body

PropertyTypeRequiredDescription
contactsarrayrequiredListe de contacts à upserter (max 100 par requête, sinon `400 batch_too_large`).
system_idstringoptionalSystè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_automationsbooleanoptionalDé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

StatusDescription
200

Batch traité. processed = nombre de contacts insérés ou mis à jour avec succès.

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 code : insufficient_scope (la clé ne possède pas le scope requis — details.required_scope) ou feature_not_available (l'API publique ou le module visé n'est pas inclus dans le forfait du compte — details.feature nomme la fonctionnalité, ex. public_api, quotes). Un refus de forfait est définitif pour la clé : inutile de réessayer, le compte doit changer de forfait.

409

Une requête précédente avec la même Idempotency-Key a utilisé un payload différent, ou est encore en cours de traitement.

415

Le header Content-Type est manquant ou non supporté pour cet endpoint.

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 request_id au support.

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."
    }
  ]
}'