Leads
Un lead es una persona con la que habla tu setter IA. La API expone la misma ficha que la pestaña Leads del dashboard: estado, etiquetas, responsable, próxima acción, resultado comercial y notas.
Endpoints
Sección titulada «Endpoints»| Método | Ruta | 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 |
Listar y filtrar leads
Sección titulada «Listar y filtrar leads»Los leads llegan del más recientemente activo al más antiguo, 25 por página por defecto.
curl "https://app.whatsetter.com/api/v1/leads?status=qualified&campaign_id=acme-espana-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-espana-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-espana-1-ab12", "limit": 25}, headers={"Authorization": "Bearer ws_live_…"},)data = r.json()["data"]{ "data": [ { "id": "66f1a2b3c4d5e6f7a8b9c0d1", "phone": "34612345678", "whatsapp_id": "34612345678@c.us", "name": "Julien", "status": "qualified", "qualified": true, "qualified_at": "2026-09-12T10:04:31.000Z", "replied": true, "campaign_id": "acme-espana-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": "Volver a llamar", "created_at": "2026-09-10T07:59:58.000Z", "updated_at": "2026-09-18T16:42:05.000Z" } ], "pagination": { "has_more": true, "next_cursor": "66f1a2b3c4d5e6f7a8b9c0d1" }}phone son solo dígitos, sin el +. Pasa cursor=next_cursor para obtener la página siguiente.
Filtros
Sección titulada «Filtros»| Parámetro | Qué hace |
|---|---|
status |
Uno de los estados de abajo. qualified devuelve todos los leads calificados en algún momento (su fecha de calificación está rellena). |
campaign_id |
El slug campaign_id de la campaña o su id, ambos vienen de GET /campaigns. |
phone |
Búsqueda exacta, formato internacional (+34612345678). |
external_reference |
Búsqueda exacta sobre el identificador que enviaste con POST /campaigns/{id}/leads. |
source |
De dónde viene el 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 |
Solo los leads traídos por este enlace rastreado. |
channel |
whatsapp o instagram. |
since |
Solo los leads activos después de esta fecha ISO 8601 (sobre last_message_at). |
limit, cursor |
Paginación, de 1 a 100 por página. |
Valores de estado
Sección titulada «Valores de estado»status |
Significado |
|---|---|
queued |
Importado, esperando su primer mensaje. |
pending |
El agente se detuvo y espera a tu equipo. |
in_progress |
La conversación está en marcha. |
qualified |
El agente validó el interés, o lo fijaste desde la API. |
not_interested |
Perfil equivocado o rechazo explícito. |
cold |
Sin respuesta tras los seguimientos. |
stopped |
La conversación se detuvo. |
unknown |
Un valor interno que la API pública no nombra. |
Leer un lead
Sección titulada «Leer 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 respuesta es { "data": lead } con el mismo objeto de arriba. Un lead de otro espacio de trabajo responde 404, nunca 403.
Actualizar un lead
Sección titulada «Actualizar un lead»Envía solo los campos que cambias. Se necesita al menos uno.
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": "Volver a llamar" }'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: 'Volver a llamar', }),});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": "Volver a llamar", },)lead = r.json()["data"]La respuesta es el lead actualizado, 200.
| Campo | Reglas |
|---|---|
status |
qualified, in_progress, not_interested, cold, pending, stopped o queued. Fijar qualified rellena qualified_at una sola vez. |
tags |
Array de hasta 20 cadenas, 40 caracteres cada una. Sustituye toda la lista. Las etiquetas vacías o demasiado largas se descartan, los duplicados se eliminan. |
assigned_to |
Un id de miembro de GET /team/members, o null para quitar el responsable. Id desconocido: 422. |
next_action_at |
Fecha ISO 8601, o null. |
next_action_label |
Hasta 80 caracteres, o null. Necesita una fecha next_action_at (422 si falta). Borrar la fecha borra la etiqueta. |
deal_status |
open, won o lost. |
lost_reason |
Uno de price, timing, unqualified, competitor, unreachable, other. Obligatorio con deal_status: "lost", se borra con open y won. |
Cada cambio escribe el historial del lead, los mismos eventos que escribe el dashboard (owner, next_action, outcome), con el nombre de tu clave API como autor.
Las notas son los textos libres que tu equipo ve en la ficha del 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":"Le devolví la llamada, quiere un presupuesto antes del viernes."}'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: 'Le devolví la llamada, quiere un presupuesto antes del viernes.' }),});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": "Le devolví la llamada, quiere un presupuesto antes del viernes."},)note = r.json()["data"]{ "data": { "id": "66f1a2b3c4d5e6f7a8b9c0e2", "text": "Le devolví la llamada, quiere un presupuesto antes del viernes.", "author": "API · Mi CRM", "created_at": "2026-09-19T08:12:00.000Z" }}text tiene de 1 a 2000 caracteres. El autor es API · <nombre de la clave> para las notas que creas, o el nombre del compañero para las notas escritas en el dashboard. GET /leads/{id}/notes devuelve { "data": [ ...notas ] }, las más recientes primero, hasta 100.
Pasar una conversación a una persona y devolverla
Sección titulada «Pasar una conversación a una persona y devolverla»POST /leads/{id}/handoff detiene al agente IA en este lead. Se queda en silencio hasta que llames a POST /leads/{id}/resume. Es el mismo interruptor que la toma de control del dashboard, así que los dos nunca se contradicen.
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" }}- El
statusdel lead se conserva: tomar el control nunca marca un lead como detenido o calificado. - Las dos llamadas son idempotentes: un segundo handoff sobre un lead ya tomado responde
200. dashboard_synced: falsesignifica que el agente está detenido pero la lista de Leads puede ir con retraso. La vista de conversación siempre es correcta.- La regla de estado: handoff y resume solo se aplican a leads con los que el agente ya ha hablado (
in_progress,stopped,cold,qualified,not_interested). Un leadqueuedopendingno tiene conversación que pasar y responde409 invalid_state.
El campo stage
Sección titulada «El campo stage»stage es de solo lectura y se deriva del lead exactamente como el embudo del dashboard. Responde en una palabra a «¿dónde está esta persona comercialmente?», mientras que status es el vocabulario propio de la IA.
stage |
Cuándo |
|---|---|
contacted |
Contactado, todavía sin respuesta. |
conversation |
El lead respondió, la IA o una persona le está hablando. |
waiting |
La IA se detuvo y espera a tu equipo (status: pending). |
qualified |
Interés validado (status: qualified). |
booked |
Hay una reunión en el calendario, o ya se celebró. Gana a cualquier otro valor. |
not_interested |
Perfil equivocado o rechazo. |
cold |
Sin respuesta tras los seguimientos. |
stopped |
La conversación se detuvo. |
Enlazar con tu CRM
Sección titulada «Enlazar con tu CRM»Envía el identificador de tu propia ficha en external_reference cuando empujes un lead opt-in con POST /campaigns/{id}/leads. WhatSetter lo guarda tal cual, lo devuelve en cada lead y cada webhook, y te deja buscarlo:
curl "https://app.whatsetter.com/api/v1/leads?external_reference=4471" \ -H "Authorization: Bearer ws_live_…"external_reference es null para los leads que no vienen de ti (importaciones CSV, importaciones de lista, formularios de página de venta). Para esos, compara por phone: solo dígitos en ambos lados.
Errores que encontrarás
Sección titulada «Errores que encontrarás»| HTTP | code |
Cuándo |
|---|---|---|
| 401 | missing_api_key, invalid_api_key |
Clave ausente, desconocida o revocada. |
| 403 | insufficient_scope |
La clave no tiene leads:read o leads:write. |
| 404 | not_found |
Lead desconocido, o lead de otro espacio de trabajo. |
| 409 | invalid_state |
Handoff o resume sobre un lead nunca contactado. |
| 422 | validation_error |
Estado desconocido, teléfono inválido, id de miembro desconocido, cuerpo vacío, cursor inválido o caducado. |
| 429 | rate_limited |
Demasiadas peticiones este minuto. Respeta Retry-After. |
| 502 | upstream_error |
El flujo de control del agente no estaba disponible. Puedes reintentar. |

