EN

Leads

1 endpoint

post/v1/leads/capture

Capturer un lead depuis un funnel externe

Capture un nouveau lead dans Capturia depuis un funnel, formulaire ou outil tiers (Make, Zapier, n8n, landing custom). Une fois capturé, le lead apparaît immédiatement dans Mes Prospects côté dashboard et le bot SMS prend le relais dans les 30 secondes si le consentement et la configuration sont en ordre.

Contact déjà connu (courriel ou téléphone)

La fiche est résolue par courriel d'abord (insensible à la casse), puis par téléphone, en incluant les coordonnées secondaires déjà rattachées à une fiche. Capturer un lead dont le courriel ou le téléphone existe déjà réutilise et enrichit la fiche existante — le contact_id retourné est celui de cette fiche, jamais un doublon et jamais une erreur. Les coordonnées et champs cœur déjà remplis (nom, courriel, téléphone, pipeline, valeur, adresse...) ne sont pas écrasés ; les réponses de qualification (custom_fields) sont en revanche rafraîchies avec les dernières valeurs envoyées — une clé déjà présente est remplacée, une clé nouvelle s'ajoute. Un nouveau téléphone est conservé comme numéro secondaire quand la fiche en a déjà un. Si le courriel et le téléphone pointent deux fiches différentes, le courriel a priorité et aucune coordonnée n'est retirée à l'autre fiche.

La réponse expose contact_was_new pour distinguer les deux issues : sur une fiche existante (false), pipeline et assignment_status décrivent la demande résolue, pas forcément l'état persisté — la fiche conserve son pipeline, son étape et son vendeur déjà en place.

Contact déjà en pipeline ouvert

Un lead dont le contact (résolu par courriel ou téléphone) est déjà dans une étape de pipeline ouverte (déjà pris en charge par un vendeur — non archivé, étape ni gagnée ni perdue) n'est pas re-soumis : la réponse renvoie status: existing_pipeline_contact, aucune nouvelle soumission n'est enregistrée et le bot SMS n'est pas redéclenché, pour ne pas écraser une conversation en cours. Les champs cœur nouvellement fournis enrichissent quand même le contact existant.

La dédup de retry reste prioritaire : un re-push reçu dans la fenêtre de 5 minutes est traité comme un retry (status: idempotent_replay, voir Idempotency) et conserve l'activity_id et l'original_created_at d'origine — existing_pipeline_contact ne s'applique qu'au-delà de cette fenêtre.

Idempotency

Le header Idempotency-Key est optionnel mais recommandé pour les retries côté client. Une 2e requête avec la même clé et le même payload renvoie la réponse d'origine sans dupliquer le lead. La clé est stockée 24h. Une 2e requête avec la même clé mais un payload différent retourne 409 idempotency_conflict. La RPC sous-jacente conserve par ailleurs sa propre fenêtre de dedup à 5 minutes. Cette fenêtre est par contact et indépendante du contenu : toute soumission d'un contact déjà capturé dans les 5 dernières minutes est traitée comme un retry (idempotent_replay) même si le payload diffère — la fiche est tout de même enrichie (dont les custom_fields, rafraîchis), mais aucune nouvelle activité, aucun tag, et pas de relance du bot SMS. Les mises à jour de la fiche restent réelles : une automatisation qui surveille un champ personnalisé réagit au changement de valeur, comme pour toute modification de la fiche. C'est aussi ce qui protège d'une double relance du bot quand un lead re-soumet son formulaire coup sur coup. Un Idempotency-Key différent ne contourne pas cette fenêtre : elle s'applique en base après tout cache miss. Deux soumissions volontairement distinctes du même contact ne produisent deux activités que si elles sont espacées de plus de 5 minutes.

Codes d'erreur custom

Pour des raisons historiques, certains rejets retournent des codes hors catalog standard :

  • 422invalid_phone, missing_consent, invalid_email, invalid_payload, pipeline_not_found, stage_not_found (au lieu de validation_error / business_rule_violation). Body shape simplifiée propre à cette route — voir la réponse 422 ci-dessous.
  • 429rate_limited_ip quand la limite IP (100 req/min) est atteinte (au lieu de rate_limit_exceeded). Le header X-RateLimit-Scope: ip identifie le scope de la limite déclenchée. Body shape simplifiée (pas de request_id ni details standard) — à harmoniser dans une phase ultérieure.
Scopes requis: leads:capture

Corps de requête

#/components/schemas/LeadCapturePayload

Responses

StatusDescription
200

Lead capturé (status: created), replay idempotent d'une capture précédente (status: idempotent_replay), ou contact déjà pris en charge (status: existing_pipeline_contact — déjà dans une étape de pipeline ouverte : aucune nouvelle soumission n'est enregistrée et le bot SMS n'est pas déclenché, activity_id est null). Dans tous les cas, le contact_id retourné est stable.

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.

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.

422

Payload refusé par la validation (invalid_phone, missing_consent, invalid_email, invalid_payload) ou par la résolution du pipeline (pipeline_not_found, stage_not_found). Forme simplifiée propre à cette route : pas de type ni de doc_url, et details est un tableau [{ field, reason }] (field vide quand le body n'est pas un objet). Pour un champ scalaire fautif, le message renvoie en écho la valeur reçue (bornée à 120 caractères) — elle n'est jamais conservée côté Capturia ; seuls les noms des champs reçus le sont, visibles dans l'onglet Erreurs récentes de la clé API. Le request_id est toujours dans l'en-tête X-Request-ID.

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.

Exemple de requête

curl -X POST 'https://app.capturia.io/api/v1/leads/capture' \
  -H 'Authorization: Bearer cap_live_<your-key>' \
  -H 'Content-Type: application/json' \
  -d '{
  "phone": "+15145551234",
  "sms_consent": true,
  "first_name": "Alex",
  "last_name": "Tremblay",
  "email": "[email protected]",
  "source_label": "funnel-fb-jan",
  "utm": {
    "source": "facebook",
    "medium": "cpc",
    "campaign": "spring-2026"
  },
  "tags": [
    "lead-chaud",
    "vu-webinaire"
  ],
  "pipeline": {
    "pipeline_slug": "ventes-b2b",
    "stage_slug": "nouveau-lead",
    "deal_value": 2500,
    "expected_close_date": "2026-06-30",
    "assigned_sales_rep_email": "[email protected]"
  },
  "custom_fields": {
    "type_propriete": "maison_isolee",
    "delai_vente": "0_3_mois"
  },
  "address": "123 rue Principale, Montréal",
  "postal_code": "H2X 1Y4"
}'