---
title: Pipelines
description: Read, create, rename and delete Deal pipelines and their stages.
product: signal
section: api
order: 13
---

# Pipelines

A pipeline is an ordered set of stages that [Deals](deals.md) move through. Every workspace has one default pipeline.

## The pipeline object

| Field | Type | Notes |
| --- | --- | --- |
| `id` | string | UUID. |
| `name` | string | |
| `description` | string or null | |
| `is_default` | boolean | `true` for the workspace's default pipeline. |
| `stages` | array | The pipeline's stages, in order. See below. |
| `created_at` | string | ISO 8601 timestamp. |
| `updated_at` | string | ISO 8601 timestamp. |

Each stage:

| Field | Type | Notes |
| --- | --- | --- |
| `id` | string | UUID of the pipeline stage. This is **not** a Deal's `stage_id`; see [Stage IDs](deals.md#stage-ids). |
| `name` | string | |
| `code` | string | Links the stage to its deal stage ID. |
| `sort_order` | integer | Position in the pipeline, lowest first. |
| `is_won` | boolean | Deals at this stage count as won. |
| `is_lost` | boolean | Deals at this stage count as lost. |

```json
{
  "id": "0d9b7f5e-3c1a-4e8f-a6b4-2d0c8e6a4f92",
  "name": "New business",
  "description": null,
  "is_default": true,
  "created_at": "2026-03-02T18:21:07.000Z",
  "updated_at": "2026-03-02T18:21:07.000Z",
  "stages": [
    { "id": "e2c4a6b8-0d1f-4a3c-9e5b-7d9f1b3d5f70", "name": "Lead In", "code": "lead_in", "sort_order": 0, "is_won": false, "is_lost": false },
    { "id": "f3d5b7c9-1e2a-4b4d-8f6c-8eaf2c4e6a81", "name": "Discovery", "code": "discovery", "sort_order": 1, "is_won": false, "is_lost": false },
    { "id": "a4e6c8d0-2f3b-4c5e-9a7d-9fb03d5f7b92", "name": "Proposal", "code": "proposal", "sort_order": 2, "is_won": false, "is_lost": false },
    { "id": "b5f7d9e1-3a4c-4d6f-8b8e-a0c14e6a8ca3", "name": "Negotiation", "code": "negotiation", "sort_order": 3, "is_won": false, "is_lost": false },
    { "id": "c6a8e0f2-4b5d-4e7a-9c9f-b1d25f7b9db4", "name": "Closed Won", "code": "closed_won", "sort_order": 4, "is_won": true, "is_lost": false },
    { "id": "d7b9f1a3-5c6e-4f8b-8da0-c2e36a8caec5", "name": "Closed Lost", "code": "closed_lost", "sort_order": 5, "is_won": false, "is_lost": true }
  ]
}
```

---

## List pipelines

`GET /pipelines` · Scope: `pipeline:read`

Returns pipelines newest first, each with its stages.

### Query parameters

| Parameter | Type | Required | Notes |
| --- | --- | --- | --- |
| `limit` | integer | No | 1 to 100. Default 20. |
| `cursor` | string | No | `meta.cursor` from the previous page. |

### Example

```bash
curl https://crm.empiraa.app/api/v1/pipelines \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
const res = await fetch("https://crm.empiraa.app/api/v1/pipelines", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const { data } = await res.json();
const defaultPipeline = data.find((p) => p.is_default);
```

### Response `200`

An array of [pipeline objects](#the-pipeline-object) in `data`; `meta` has `cursor` and `hasMore`.

---

## Create a pipeline

`POST /pipelines` · Scope: `pipeline:write`

Creates a pipeline with the stages you send. New pipelines are never the default.

### Request body

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `name` | string | Yes | |
| `description` | string or null | No | |
| `stages` | array | No | The stages, in order. **If you leave this out, the pipeline has no stages**, and Deals can't be placed in it. |
| `stages[].name` | string | Yes, per stage | |
| `stages[].code` | string | Yes, per stage | A short identifier such as `demo_booked`. Reuse a standard code (such as `proposal`) to share its deal stage ID. |
| `stages[].sort_order` | integer | No | Defaults to the stage's position in the array. |
| `stages[].is_won` | boolean | No | Default `false`. |
| `stages[].is_lost` | boolean | No | Default `false`. |

### Example

```bash
curl -X POST https://crm.empiraa.app/api/v1/pipelines \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Partnerships",
    "stages": [
      { "name": "Discovery", "code": "discovery" },
      { "name": "Proposal", "code": "proposal" },
      { "name": "Signed", "code": "closed_won", "is_won": true },
      { "name": "Lost", "code": "closed_lost", "is_lost": true }
    ]
  }'
```

```js
const res = await fetch("https://crm.empiraa.app/api/v1/pipelines", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({
    name: "Partnerships",
    stages: [
      { name: "Discovery", code: "discovery" },
      { name: "Proposal", code: "proposal" },
      { name: "Signed", code: "closed_won", is_won: true },
      { name: "Lost", code: "closed_lost", is_lost: true },
    ],
  }),
});
```

### Response `201`

The new [pipeline object](#the-pipeline-object), with its stages, in `data`.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `INVALID_REQUEST` | `name` is missing or not a string. |

---

## Get a pipeline

`GET /pipelines/{id}` · Scope: `pipeline:read`

### Path parameters

| Parameter | Type | Notes |
| --- | --- | --- |
| `id` | string | Pipeline ID. |

### Example

```bash
curl https://crm.empiraa.app/api/v1/pipelines/0d9b7f5e-3c1a-4e8f-a6b4-2d0c8e6a4f92 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
await fetch("https://crm.empiraa.app/api/v1/pipelines/0d9b7f5e-3c1a-4e8f-a6b4-2d0c8e6a4f92", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
```

### Response `200`

The [pipeline object](#the-pipeline-object) in `data`.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `NOT_FOUND` | No pipeline with that ID in this workspace. |

---

## Update a pipeline

`PATCH /pipelines/{id}` · Scope: `pipeline:write`

Renames a pipeline or changes its description. Stages can't be changed through the API; edit them in Signal.

### Request body

| Field | Type | Notes |
| --- | --- | --- |
| `name` | string | |
| `description` | string or null | |

### Example

```bash
curl -X PATCH https://crm.empiraa.app/api/v1/pipelines/0d9b7f5e-3c1a-4e8f-a6b4-2d0c8e6a4f92 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Direct sales"}'
```

```js
await fetch("https://crm.empiraa.app/api/v1/pipelines/0d9b7f5e-3c1a-4e8f-a6b4-2d0c8e6a4f92", {
  method: "PATCH",
  headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Direct sales" }),
});
```

### Response `200`

The updated [pipeline object](#the-pipeline-object), with its stages, in `data`.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `NOT_FOUND` | No pipeline with that ID in this workspace. |

---

## Delete a pipeline

`DELETE /pipelines/{id}` · Scope: `pipeline:write`

Deletes a pipeline and its stages. You can't delete the default pipeline, or a pipeline that still has Deals in it or Deals using its stages. Move those Deals to another pipeline first.

### Example

```bash
curl -X DELETE https://crm.empiraa.app/api/v1/pipelines/0d9b7f5e-3c1a-4e8f-a6b4-2d0c8e6a4f92 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
await fetch("https://crm.empiraa.app/api/v1/pipelines/0d9b7f5e-3c1a-4e8f-a6b4-2d0c8e6a4f92", {
  method: "DELETE",
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
```

### Response `204`

No body.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `INVALID_REQUEST` | The pipeline is the workspace's default ("Cannot delete the default pipeline"). |
| 404 | `NOT_FOUND` | No pipeline with that ID in this workspace. |
| 500 | `SERVER_ERROR` | The pipeline still has Deals. |

