> ## Documentation Index
> Fetch the complete documentation index at: https://developers.flowestate.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Leads

> Crear, leer, actualizar, eliminar y buscar leads. Agregar notas y comunicaciones.

El lead es el recurso central de FlowEstate. Cada endpoint en esta página opera dentro del tenant de la organización que llama — no puedes leer ni escribir leads de otra organización con la misma key.

***

## Listar leads

```http theme={null}
GET /leads
```

**Scope:** `leads:read`

### Query parameters

| Parámetro                               | Tipo   | Notas                                                                                                      |
| --------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------- |
| `limit`, `offset`, `sortOrder`, `query` | —      | Ver [Paginación](/es/pagination). `query` matchea nombre, apellido, email, empresa y dígitos del teléfono. |
| `sortBy`                                | string | Uno de `createdAt`, `firstName`, `email`, `company`, `status`, `source`, `estimatedValue`.                 |
| `status`                                | enum   | Filtrar por [`LeadStatus`](/es/enums#leadstatus).                                                          |
| `source`                                | enum   | Filtrar por [`LeadSource`](/es/enums#leadsource).                                                          |

### Response 200

```json theme={null}
{
  "data": [
    {
      "id": "lead_...",
      "firstName": "Ada",
      "lastName": "Lovelace",
      "email": "ada@example.com",
      "phone": "+18095551234",
      "company": "Analytical Engines",
      "jobTitle": "CTO",
      "status": "new",
      "source": "website",
      "stageId": null,
      "stageName": null,
      "estimatedValue": null,
      "score": null,
      "temperature": null,
      "notes": null,
      "city": null,
      "state": null,
      "country": "DO",
      "projectId": null,
      "projectName": null,
      "unitId": null,
      "assignedToId": null,
      "assignedTo": null,
      "customFields": null,
      "createdAt": "2026-04-29T18:04:15.000Z",
      "updatedAt": "2026-04-29T18:04:15.000Z"
    }
  ],
  "pagination": { "total": 327, "limit": 50, "offset": 0, "hasMore": true }
}
```

***

## Crear un lead

```http theme={null}
POST /leads
```

**Scope:** `leads:write`

Al menos uno de `email` o `phone` es requerido. Las cadenas vacías cuentan como ausentes.

### Request body

```json theme={null}
{
  "firstName": "Ada",
  "lastName": "Lovelace",
  "email": "ada@example.com",
  "phone": "+18095551234",
  "company": "Analytical Engines",
  "jobTitle": "CTO",
  "status": "new",
  "source": "meta",
  "stageId": "uuid-u-omitido",
  "estimatedValue": 250000,
  "score": 80,
  "temperature": "hot",
  "notes": "Vino de audiencia Lookalike",
  "city": "Punta Cana",
  "state": "La Altagracia",
  "country": "DO",
  "projectId": "uuid-u-omitido",
  "unitId": "uuid-u-omitido",
  "roundRobinId": "uuid-u-omitido",
  "assignedToId": "uuid-u-omitido",
  "customFields": {
    "campaignName": "Q3-launch",
    "leadId": "2442330062872321"
  }
}
```

<Note>**Semántica del campo `source`.** FlowEstate acepta cualquier string como `source`. Los valores conocidos de [`LeadSource`](/es/enums#leadsource) se guardan tal cual. Los desconocidos se guardan como `other` en la columna enum, con la cadena original preservada en `sourceMetadata.providedSource`. Esto te permite mantener tu propio mapeo de UTM/campaña sin perder datos.</Note>

### Distribución por round robin

`POST /leads` ejecuta el round robin **de punta a punta en una sola llamada**: crea el lead, lo asigna a través de la rotación de un pool y emite `lead.assigned` para que tu automatización downstream (por ejemplo, una notificación de WhatsApp al agente) se dispare. No hace falta el webhook universal de leads ni un bridge intermedio.

| Campo          | Tipo   | Notas                                                                                                                     |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------- |
| `assignedToId` | string | UUID opcional. Asigna el lead directamente a este miembro, salteando el round robin. Debe ser miembro de tu organización. |
| `roundRobinId` | string | UUID opcional. Asigna a través de la rotación de este pool específico.                                                    |

Cuando el lead no tiene responsable, la asignación se resuelve en este orden:

1. **`assignedToId`** — se asigna directamente a ese miembro.
2. **`roundRobinId`** — se asigna a través de ese pool. Como el destino es explícito, se respeta aunque el pool no tenga conexiones, y se omiten los flags `assignInboundLeads` / `assignManualLeads` del pool.
3. **Ninguno** — asignación automática genérica: el lead se enruta a un pool activo con `assignInboundLeads` habilitado. Un pool **sin conexiones** funciona como el pool "inbound genérico" comodín, así que puedes distribuir leads de la API sin configurar ninguna conexión de webhook.

Si ningún pool coincide —o el pool encontrado no tiene participante elegible— el lead se crea **sin asignar**. La asignación es best-effort: nunca falla la creación del lead.

### Response 201

`assignedToId` es el miembro asignado (o `null` si el lead quedó sin asignar). `roundRobinId` es el pool a través del cual se asignó el lead (`null` para un `assignedToId` explícito o cuando no hubo asignación).

```json theme={null}
{
  "id": "lead_...",
  "firstName": "Ada",
  "lastName": "Lovelace",
  "email": "ada@example.com",
  "phone": "+18095551234",
  "company": "Analytical Engines",
  "status": "new",
  "source": "meta",
  "assignedToId": "uuid",
  "roundRobinId": "uuid",
  "createdAt": "2026-04-29T18:04:15.000Z",
  "updatedAt": "2026-04-29T18:04:15.000Z"
}
```

### Errores comunes

* `400 VALIDATION_ERROR` — email inválido, falta tanto email como teléfono, o `assignedToId` no es miembro de esta organización (se valida antes de crear el lead).
* `403 FORBIDDEN` — límite del plan alcanzado. El mensaje identifica el límite.

### Efectos secundarios de webhooks

* Siempre: `lead.created` (con `X-FlowEstate-Source: api`).
* Si el lead queda asignado (vía `assignedToId`, `roundRobinId`, o asignación automática genérica): `lead.assigned`. Su flag `autoAssigned` es `true` cuando el round robin eligió al participante, y `false` para un `assignedToId` explícito — para distinguir ambos casos downstream.

***

## Obtener un lead

```http theme={null}
GET /leads/{leadId}
```

**Scope:** `leads:read`. Devuelve la misma forma que un item de la lista. `404 NOT_FOUND` si el lead no existe o pertenece a otra organización.

***

## Actualizar un lead

```http theme={null}
PUT /leads/{leadId}
```

**Scope:** `leads:write`. Mismo body que crear, todos los campos opcionales. Debes proveer al menos un campo. La regla de identidad de contacto (email o teléfono) sigue aplicando tras el merge.

Pasa `null` para limpiar un campo; omítelo para dejarlo intacto.

### Efectos secundarios de webhooks

* Siempre: `lead.updated`.
* Si cambió `stageId`: `lead.stage_changed`.
* Si cambió `status` a `won`: `lead.won`. A `lost`: `lead.lost`.
* Si cambió `assignedToId`: `lead.assigned` (cuando se setea) o `lead.unassigned` (cuando se limpia).

***

## Eliminar un lead

```http theme={null}
DELETE /leads/{leadId}
```

**Scope:** `leads:write`.

### Response 200

```json theme={null}
{ "deleted": true, "id": "lead_..." }
```

### Efectos secundarios de webhooks

Se dispara un evento `lead.deleted`.

***

## Buscar un lead

```http theme={null}
GET /leads/search
```

**Scope:** `leads:read`. Diseñado para los pasos "Find Lead" de Zapier / Make.

### Query parameters

Al menos uno de estos es requerido:

| Parámetro | Descripción                                                                                                     |
| --------- | --------------------------------------------------------------------------------------------------------------- |
| `email`   | Match exacto (normalizado a minúsculas).                                                                        |
| `phone`   | Matchea contra la forma solo-dígitos de los teléfonos guardados (así `+1 (809) 555-1234` matchea `8095551234`). |
| `q`       | Match difuso sobre nombre/apellido, email, empresa.                                                             |
| `limit`   | 1..50, default `10`.                                                                                            |

### Response 200

```json theme={null}
{
  "data": [
    {
      "id": "lead_...",
      "firstName": "Ada",
      "lastName": "Lovelace",
      "email": "ada@example.com",
      "phone": "+18095551234",
      "company": "Analytical Engines",
      "status": "new",
      "source": "website",
      "stageId": null,
      "stageName": null,
      "assignedTo": null,
      "createdAt": "2026-04-29T18:04:15.000Z"
    }
  ]
}
```

***

## Listar actividades

```http theme={null}
GET /leads/{leadId}/activities
```

**Scope:** `leads:read`. Lista actividades del timeline (notas, llamadas, correos, reuniones, eventos del sistema).

### Query parameters

| Parámetro       | Notas                                                         |
| --------------- | ------------------------------------------------------------- |
| `limit`         | 1..100, default `20`.                                         |
| `offset`        | Default `0`.                                                  |
| `type`          | Filtrar por [`LeadActivityType`](/es/enums#leadactivitytype). |
| `activityScope` | Filtrar por `LeadActivityScope`. Default `lead_event`.        |

### Response 200

```json theme={null}
{
  "data": [
    {
      "id": "act_...",
      "leadId": "lead_...",
      "type": "note",
      "activityScope": "lead_event",
      "content": "Llamé y dejé buzón de voz",
      "changes": null,
      "communicationType": null,
      "communicationDirection": null,
      "user": { "id": "user_...", "name": "...", "email": "...", "image": null },
      "createdAt": "2026-04-29T18:04:15.000Z"
    }
  ],
  "pagination": { "total": 12, "limit": 20, "offset": 0, "hasMore": false }
}
```

***

## Agregar una nota

```http theme={null}
POST /leads/{leadId}/notes
```

**Scope:** `leads:write`.

### Request body

```json theme={null}
{ "content": "Hablamos hoy, muy interesado" }
```

`content` es requerido, 1..5000 caracteres.

### Response 201

```json theme={null}
{
  "id": "act_...",
  "leadId": "lead_...",
  "content": "Hablamos hoy, muy interesado",
  "createdAt": "2026-04-29T18:04:15.000Z"
}
```

### Efectos secundarios de webhooks

Se dispara un evento `lead.note_added`.

***

## Registrar una comunicación

```http theme={null}
POST /leads/{leadId}/communications
```

**Scope:** `leads:write`. Registra una llamada, correo, reunión o mensaje de redes sociales en el timeline del lead.

### Request body

```json theme={null}
{
  "type": "call",
  "direction": "outbound",
  "content": "Conversamos sobre disponibilidad de unidades"
}
```

| Campo       | Requerido | Notas                                                |
| ----------- | --------- | ---------------------------------------------------- |
| `type`      | sí        | Uno de `call`, `email`, `meeting`, `social_message`. |
| `direction` | sí        | `inbound` o `outbound`.                              |
| `content`   | no        | Texto libre, ≤ 5000 caracteres.                      |

### Response 201

```json theme={null}
{
  "id": "act_...",
  "leadId": "lead_...",
  "type": "call",
  "direction": "outbound",
  "content": "Conversamos sobre disponibilidad de unidades",
  "createdAt": "2026-04-29T18:04:15.000Z"
}
```

### Efectos secundarios de webhooks

Se dispara un evento `lead.communication_logged`.
