> ## 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

> Create, read, update, delete, and search leads. Add notes and communications.

The lead is the central resource of FlowEstate. Every endpoint on this page operates inside the calling organization's tenant — you cannot read or write leads from another organization with the same key.

***

## List leads

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

**Scope:** `leads:read`

### Query parameters

| Param                                   | Type   | Notes                                                                                                   |
| --------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------- |
| `limit`, `offset`, `sortOrder`, `query` | —      | See [Pagination](/pagination). `query` matches first name, last name, email, company, and phone digits. |
| `sortBy`                                | string | One of `createdAt`, `firstName`, `email`, `company`, `status`, `source`, `estimatedValue`.              |
| `status`                                | enum   | Filter by [`LeadStatus`](/enums#leadstatus).                                                            |
| `source`                                | enum   | Filter by [`LeadSource`](/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 }
}
```

***

## Create a lead

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

**Scope:** `leads:write`

At least one of `email` or `phone` is required. Empty strings are treated as missing.

### 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-or-omitted",
  "estimatedValue": 250000,
  "score": 80,
  "temperature": "hot",
  "notes": "Came from Lookalike audience",
  "city": "Punta Cana",
  "state": "La Altagracia",
  "country": "DO",
  "projectId": "uuid-or-omitted",
  "unitId": "uuid-or-omitted",
  "roundRobinId": "uuid-or-omitted",
  "assignedToId": "uuid-or-omitted",
  "customFields": {
    "campaignName": "Q3-launch",
    "leadId": "2442330062872321"
  }
}
```

<Note>**Source field semantics.** FlowEstate accepts any string for `source`. Known [`LeadSource`](/enums#leadsource) values are stored as-is. Unknown values are stored as `other` on the enum column, with the original string preserved in `sourceMetadata.providedSource`. This lets you keep your own UTM/campaign mapping without losing data.</Note>

### Round robin distribution

`POST /leads` runs the round robin **end to end in a single call** — it creates the lead, assigns it through a pool's rotation, and emits `lead.assigned` so your downstream automation (for example, an agent WhatsApp notification) fires. No universal lead webhook or middleware bridge is required.

| Field          | Type   | Notes                                                                                                                    |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------ |
| `assignedToId` | string | Optional UUID. Assign the lead directly to this member, skipping the round robin. Must be a member of your organization. |
| `roundRobinId` | string | Optional UUID. Assign through this specific pool's rotation.                                                             |

When the lead has no owner, assignment is resolved in this order:

1. **`assignedToId`** — assigned directly to that member.
2. **`roundRobinId`** — assigned through that pool. The target is explicit, so it is honored even if the pool has no connections, and the pool's `assignInboundLeads` / `assignManualLeads` flags are bypassed.
3. **Neither** — generic auto-assignment: the lead is routed to an active pool with `assignInboundLeads` enabled. A pool with **no connections** acts as the catch-all "generic inbound" pool, so you can distribute API leads without configuring any webhook connection.

If no pool matches — or the matched pool has no eligible participant — the lead is created **unassigned**. Assignment is best-effort: it never fails lead creation.

### Response 201

`assignedToId` is the assigned member (or `null` if the lead was left unassigned). `roundRobinId` is the pool the lead was assigned through (`null` for an explicit `assignedToId` or when no assignment happened).

```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"
}
```

### Common errors

* `400 VALIDATION_ERROR` — invalid email, both email and phone missing, or `assignedToId` is not a member of this organization (validated before the lead is created).
* `403 FORBIDDEN` — plan limit reached. Message identifies the limit.

### Webhook side effects

* Always: `lead.created` (dispatched with `X-FlowEstate-Source: api`).
* If the lead is assigned (via `assignedToId`, `roundRobinId`, or generic auto-assignment): `lead.assigned`. Its `autoAssigned` flag is `true` when the round robin picked the participant and `false` for an explicit `assignedToId`, so you can distinguish the two downstream.

***

## Get a lead

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

**Scope:** `leads:read`. Returns the same shape as a list item. `404 NOT_FOUND` if the lead doesn't exist or belongs to another org.

***

## Update a lead

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

**Scope:** `leads:write`. Same body as create, all fields optional. Must provide at least one field. The contact-identity rule (email or phone) still applies after the merge.

Pass `null` to clear a field; omit it to leave it untouched.

### Webhook side effects

* Always: `lead.updated`.
* If `stageId` changed: `lead.stage_changed`.
* If `status` changed to `won`: `lead.won`. To `lost`: `lead.lost`.
* If `assignedToId` changed: `lead.assigned` (when set) or `lead.unassigned` (when cleared).

***

## Delete a lead

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

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

### Response 200

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

### Webhook side effects

A `lead.deleted` event is dispatched.

***

## Search a lead

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

**Scope:** `leads:read`. Designed for Zapier / Make "Find Lead" steps.

### Query parameters

At least one of these is required:

| Param   | Description                                                                                         |
| ------- | --------------------------------------------------------------------------------------------------- |
| `email` | Exact match (normalized to lowercase).                                                              |
| `phone` | Matches against the digit-only form of stored phones (so `+1 (809) 555-1234` matches `8095551234`). |
| `q`     | Fuzzy match on first/last name, email, company.                                                     |
| `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"
    }
  ]
}
```

***

## List activities

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

**Scope:** `leads:read`. Lists timeline activities (notes, calls, emails, meetings, system events).

### Query parameters

| Param           | Notes                                                    |
| --------------- | -------------------------------------------------------- |
| `limit`         | 1..100, default `20`.                                    |
| `offset`        | Default `0`.                                             |
| `type`          | Filter by [`LeadActivityType`](/enums#leadactivitytype). |
| `activityScope` | Filter by `LeadActivityScope`. Defaults to `lead_event`. |

### Response 200

```json theme={null}
{
  "data": [
    {
      "id": "act_...",
      "leadId": "lead_...",
      "type": "note",
      "activityScope": "lead_event",
      "content": "Called and left voicemail",
      "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 }
}
```

***

## Add a note

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

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

### Request body

```json theme={null}
{ "content": "Spoke today, very interested" }
```

`content` is required, 1..5000 chars.

### Response 201

```json theme={null}
{
  "id": "act_...",
  "leadId": "lead_...",
  "content": "Spoke today, very interested",
  "createdAt": "2026-04-29T18:04:15.000Z"
}
```

### Webhook side effects

A `lead.note_added` event is dispatched.

***

## Log a communication

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

**Scope:** `leads:write`. Records a phone call, email, meeting, or social-media message against the lead's timeline.

### Request body

```json theme={null}
{
  "type": "call",
  "direction": "outbound",
  "content": "Discussed unit availability"
}
```

| Field       | Required | Notes                                                |
| ----------- | -------- | ---------------------------------------------------- |
| `type`      | yes      | One of `call`, `email`, `meeting`, `social_message`. |
| `direction` | yes      | `inbound` or `outbound`.                             |
| `content`   | no       | Free text, ≤ 5000 chars.                             |

### Response 201

```json theme={null}
{
  "id": "act_...",
  "leadId": "lead_...",
  "type": "call",
  "direction": "outbound",
  "content": "Discussed unit availability",
  "createdAt": "2026-04-29T18:04:15.000Z"
}
```

### Webhook side effects

A `lead.communication_logged` event is dispatched.
