Leads
Un lead, c’est une personne à qui ton setter IA parle. L’API expose la même fiche que l’onglet Leads du dashboard : statut, tags, propriétaire, prochaine action, résultat commercial et notes.
Endpoints
Section intitulée « Endpoints »| Méthode | Chemin | Scope |
|---|---|---|
| GET | /leads |
leads:read |
| GET | /leads/{id} |
leads:read |
| PATCH | /leads/{id} |
leads:write |
| GET | /leads/{id}/notes |
leads:read |
| POST | /leads/{id}/notes |
leads:write |
| POST | /leads/{id}/handoff |
leads:write |
| POST | /leads/{id}/resume |
leads:write |
Lister et filtrer les leads
Section intitulée « Lister et filtrer les leads »Les leads arrivent du plus récemment actif au plus ancien, 25 par page par défaut.
curl "https://app.whatsetter.com/api/v1/leads?status=qualified&campaign_id=acme-france-1-ab12&limit=25" \ -H "Authorization: Bearer ws_live_…"const res = await fetch( 'https://app.whatsetter.com/api/v1/leads?status=qualified&campaign_id=acme-france-1-ab12&limit=25', { headers: { Authorization: 'Bearer ws_live_…' } },);const { data, pagination } = await res.json();import requests
r = requests.get( "https://app.whatsetter.com/api/v1/leads", params={"status": "qualified", "campaign_id": "acme-france-1-ab12", "limit": 25}, headers={"Authorization": "Bearer ws_live_…"},)data = r.json()["data"]{ "data": [ { "id": "66f1a2b3c4d5e6f7a8b9c0d1", "phone": "33612345678", "whatsapp_id": "33612345678@c.us", "name": "Julien", "status": "qualified", "qualified": true, "qualified_at": "2026-09-12T10:04:31.000Z", "replied": true, "campaign_id": "acme-france-1-ab12", "list_id": null, "channel": "whatsapp", "ig_username": null, "external_reference": "4471", "source": "api", "source_confidence": null, "tracked_link_id": null, "email": "julien@example.com", "tags": ["hot", "demo"], "messages_count": 14, "booking_status": "booked", "first_contact_at": "2026-09-10T08:00:12.000Z", "last_message_at": "2026-09-18T16:42:05.000Z", "last_message_direction": "inbound", "assigned_to": "66a1b2c3d4e5f6a7b8c9d0e1", "stage": "booked", "deal_status": "open", "lost_reason": null, "next_action_at": "2026-09-22T09:00:00.000Z", "next_action_label": "Rappeler", "created_at": "2026-09-10T07:59:58.000Z", "updated_at": "2026-09-18T16:42:05.000Z" } ], "pagination": { "has_more": true, "next_cursor": "66f1a2b3c4d5e6f7a8b9c0d1" }}phone ne contient que des chiffres, sans le +. Passe cursor=next_cursor pour obtenir la page suivante.
| Paramètre | Effet |
|---|---|
status |
Un des statuts ci-dessous. qualified renvoie tous les leads qualifiés à un moment donné (leur date de qualification est renseignée). |
campaign_id |
Le slug campaign_id de la campagne ou son id, les deux viennent de GET /campaigns. |
phone |
Recherche exacte, format international (+33612345678). |
external_reference |
Recherche exacte sur l’identifiant que tu as envoyé avec POST /campaigns/{id}/leads. |
source |
D’où vient le lead : instagram, facebook, tiktok, youtube, linkedin, x, snapchat, pinterest, google, website, email, sms, qr, ads, other, direct, list, api, webhook, inbound, manual. |
tracked_link_id |
Seulement les leads amenés par ce lien traqué. |
channel |
whatsapp ou instagram. |
since |
Seulement les leads actifs après cette date ISO 8601 (sur last_message_at). |
limit, cursor |
Pagination, 1 à 100 par page. |
Valeurs de statut
Section intitulée « Valeurs de statut »status |
Signification |
|---|---|
queued |
Importé, en attente de son premier message. |
pending |
L’agent s’est arrêté et attend ton équipe. |
in_progress |
La conversation est en cours. |
qualified |
L’agent a validé l’intérêt, ou tu l’as posé depuis l’API. |
not_interested |
Mauvaise cible ou refus explicite. |
cold |
Pas de réponse après les relances. |
stopped |
La conversation a été arrêtée. |
unknown |
Une valeur interne que l’API publique ne nomme pas. |
Lire un lead
Section intitulée « Lire un lead »curl "https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1" \ -H "Authorization: Bearer ws_live_…"const res = await fetch('https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1', { headers: { Authorization: 'Bearer ws_live_…' },});const { data: lead } = await res.json();import requests
r = requests.get( "https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1", headers={"Authorization": "Bearer ws_live_…"},)lead = r.json()["data"]La réponse est { "data": lead } avec le même objet que ci-dessus. Un lead d’un autre espace de travail répond 404, jamais 403.
Mettre à jour un lead
Section intitulée « Mettre à jour un lead »N’envoie que les champs que tu changes. Il en faut au moins un.
curl -X PATCH "https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1" \ -H "Authorization: Bearer ws_live_…" \ -H "Content-Type: application/json" \ -d '{ "status": "qualified", "tags": ["hot", "demo"], "assigned_to": "66a1b2c3d4e5f6a7b8c9d0e1", "next_action_at": "2026-09-22T09:00:00.000Z", "next_action_label": "Rappeler" }'const res = await fetch('https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1', { method: 'PATCH', headers: { Authorization: 'Bearer ws_live_…', 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'qualified', tags: ['hot', 'demo'], assigned_to: '66a1b2c3d4e5f6a7b8c9d0e1', next_action_at: '2026-09-22T09:00:00.000Z', next_action_label: 'Rappeler', }),});const { data: lead } = await res.json();import requests
r = requests.patch( "https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1", headers={"Authorization": "Bearer ws_live_…"}, json={ "status": "qualified", "tags": ["hot", "demo"], "assigned_to": "66a1b2c3d4e5f6a7b8c9d0e1", "next_action_at": "2026-09-22T09:00:00.000Z", "next_action_label": "Rappeler", },)lead = r.json()["data"]La réponse est le lead mis à jour, 200.
| Champ | Règles |
|---|---|
status |
qualified, in_progress, not_interested, cold, pending, stopped ou queued. Poser qualified renseigne qualified_at une seule fois. |
tags |
Tableau de 20 chaînes maximum, 40 caractères chacune. Remplace toute la liste. Les tags vides ou trop longs sont ignorés, les doublons retirés. |
assigned_to |
Un id de membre venant de GET /team/members, ou null pour retirer le propriétaire. Id inconnu : 422. |
next_action_at |
Date ISO 8601, ou null. |
next_action_label |
80 caractères maximum, ou null. Exige une date next_action_at (422 sinon). Effacer la date efface le libellé. |
deal_status |
open, won ou lost. |
lost_reason |
Une valeur parmi price, timing, unqualified, competitor, unreachable, other. Obligatoire avec deal_status: "lost", effacée pour open et won. |
Chaque changement écrit le journal du lead, les mêmes événements que le dashboard (owner, next_action, outcome), avec le nom de ta clé API comme acteur.
Les notes sont les textes libres que ton équipe voit sur la fiche du lead.
curl -X POST "https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1/notes" \ -H "Authorization: Bearer ws_live_…" \ -H "Content-Type: application/json" \ -d '{"text":"Rappelé, veut un devis avant vendredi."}'const res = await fetch('https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1/notes', { method: 'POST', headers: { Authorization: 'Bearer ws_live_…', 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Rappelé, veut un devis avant vendredi.' }),});const { data: note } = await res.json();import requests
r = requests.post( "https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1/notes", headers={"Authorization": "Bearer ws_live_…"}, json={"text": "Rappelé, veut un devis avant vendredi."},)note = r.json()["data"]{ "data": { "id": "66f1a2b3c4d5e6f7a8b9c0e2", "text": "Rappelé, veut un devis avant vendredi.", "author": "API · Mon CRM", "created_at": "2026-09-19T08:12:00.000Z" }}text fait 1 à 2000 caractères. L’auteur est API · <nom de la clé> pour les notes que tu crées, ou le nom du coéquipier pour les notes écrites dans le dashboard. GET /leads/{id}/notes renvoie { "data": [ ...notes ] }, les plus récentes d’abord, 100 maximum.
Passer une conversation à un humain, puis la rendre
Section intitulée « Passer une conversation à un humain, puis la rendre »POST /leads/{id}/handoff arrête l’agent IA sur ce lead. Il reste silencieux jusqu’à ce que tu appelles POST /leads/{id}/resume. C’est le même interrupteur que la prise en main du dashboard, les deux ne peuvent jamais se contredire.
curl -X POST "https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1/handoff" \ -H "Authorization: Bearer ws_live_…"const res = await fetch('https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1/handoff', { method: 'POST', headers: { Authorization: 'Bearer ws_live_…' },});const { data: control } = await res.json();import requests
r = requests.post( "https://app.whatsetter.com/api/v1/leads/66f1a2b3c4d5e6f7a8b9c0d1/handoff", headers={"Authorization": "Bearer ws_live_…"},)control = r.json()["data"]{ "data": { "id": "66f1a2b3c4d5e6f7a8b9c0d1", "external_reference": "4471", "agent_active": false, "manual_override": true, "status": "in_progress", "dashboard_synced": true, "updated_at": "2026-09-19T08:12:00.000Z" }}- Le
statusdu lead est préservé : prendre la main ne marque jamais un lead comme arrêté ou qualifié. - Les deux appels sont idempotents : un deuxième handoff sur un lead déjà pris en main répond
200. dashboard_synced: falsesignifie que l’agent est bien arrêté mais que la liste Leads peut avoir un temps de retard. La vue conversation est toujours juste.- La règle de statut : handoff et resume ne s’appliquent qu’aux leads déjà engagés par l’agent (
in_progress,stopped,cold,qualified,not_interested). Un leadqueuedoupendingn’a pas de conversation à passer et répond409 invalid_state.
Le champ stage
Section intitulée « Le champ stage »stage est en lecture seule et dérivé du lead exactement comme le funnel du dashboard. Il répond « où en est cette personne commercialement ? » en un mot, alors que status est le vocabulaire propre à l’IA.
stage |
Quand |
|---|---|
contacted |
Contacté, pas encore de réponse. |
conversation |
Le lead a répondu, l’IA ou un humain lui parle. |
waiting |
L’IA s’est arrêtée et attend ton équipe (status: pending). |
qualified |
Intérêt validé (status: qualified). |
booked |
Un rendez-vous est au calendrier, ou a eu lieu. L’emporte sur toutes les autres valeurs. |
not_interested |
Mauvaise cible ou refus. |
cold |
Pas de réponse après les relances. |
stopped |
La conversation a été arrêtée. |
Relier à ton CRM
Section intitulée « Relier à ton CRM »Envoie l’identifiant de ta propre fiche dans external_reference quand tu pousses un lead opt-in avec POST /campaigns/{id}/leads. WhatSetter le stocke tel quel, le renvoie sur chaque lead et chaque webhook, et te laisse le retrouver :
curl "https://app.whatsetter.com/api/v1/leads?external_reference=4471" \ -H "Authorization: Bearer ws_live_…"external_reference vaut null pour les leads qui ne viennent pas de toi (imports CSV, imports de liste, formulaires de page de vente). Pour ceux-là, compare sur phone : chiffres seuls des deux côtés.
Les erreurs que tu rencontreras
Section intitulée « Les erreurs que tu rencontreras »| HTTP | code |
Quand |
|---|---|---|
| 401 | missing_api_key, invalid_api_key |
Clé absente, inconnue ou révoquée. |
| 403 | insufficient_scope |
La clé n’a pas leads:read ou leads:write. |
| 404 | not_found |
Lead inconnu, ou lead d’un autre espace de travail. |
| 409 | invalid_state |
Handoff ou resume sur un lead jamais contacté. |
| 422 | validation_error |
Statut inconnu, téléphone invalide, id de membre inconnu, corps vide, curseur invalide ou expiré. |
| 429 | rate_limited |
Trop de requêtes cette minute. Respecte Retry-After. |
| 502 | upstream_error |
Le workflow de contrôle de l’agent était injoignable. Tu peux réessayer. |

