---
title: Deals
description: Create, read, update and delete Deals, and move them through pipeline stages.
product: signal
section: api
order: 12
---

# Deals

A Deal is a sales opportunity with an Account. It sits at a stage in a [pipeline](pipelines.md).

## The Deal object

| Field | Type | Notes |
| --- | --- | --- |
| `id` | string | UUID. |
| `name` | string | |
| `value` | number or null | In the workspace's currency (see `currency` on [`GET /me`](users-and-me.md)). |
| `value_period` | string | `once_off`, `monthly`, `quarterly` or `yearly`. Says whether `value` is a one-time or recurring amount. |
| `probability` | integer or null | Percent chance of winning, 0 to 100. |
| `expected_close_at` | string or null | ISO 8601 timestamp. |
| `actual_close_at` | string or null | ISO 8601 timestamp. Read-only through the API. |
| `stage_id` | string | The Deal's stage. See [Stage IDs](#stage-ids). |
| `account_id` | string | The Account the Deal is with. |
| `owner_id` | string or null | User ID of the Deal's owner. |
| `pipeline_id` | string or null | The pipeline the Deal is in. `null` means the workspace's default pipeline. |
| `created_at` | string | ISO 8601 timestamp. |
| `updated_at` | string | ISO 8601 timestamp. |

```json
{
  "id": "7c2e9f14-3a6b-4d1e-8f5a-9b0c2d4e6f81",
  "name": "Contoso renewal 2027",
  "value": 48500,
  "value_period": "yearly",
  "probability": 60,
  "expected_close_at": "2026-12-15T00:00:00.000Z",
  "actual_close_at": null,
  "stage_id": "rds_proposal",
  "account_id": "3f6c2a1e-8b4d-4c2a-9f1e-2b7d5a9c4e10",
  "owner_id": "5b8d1f3a-7e2c-4a9b-b6d4-1c3e5f7a9b20",
  "pipeline_id": null,
  "created_at": "2026-11-12T09:30:00.000Z",
  "updated_at": "2026-11-12T09:30:00.000Z"
}
```

## Stage IDs

A Deal's `stage_id` is a **deal stage ID**. It is not the `id` of a stage returned by [`GET /pipelines`](pipelines.md): pipeline stages are matched to deal stage IDs by their `code` (ignoring case).

The stage must belong to the Deal's pipeline, or to the workspace's default pipeline when the Deal has none. Otherwise the request answers `422 INVALID_STAGE`.

Deal stage IDs for Signal's standard stage codes:

| Pipeline stage `code` | `stage_id` |
| --- | --- |
| `qualification` | `rds_qualification` |
| `discovery` | `rds_discovery` |
| `proposal` | `rds_proposal` |
| `negotiation` | `rds_negotiation` |
| `closed_won` | `rds_closed_won` |
| `closed_lost` | `rds_closed_lost` |

For any other stage (including stages you created), read `stage_id` from an existing Deal in that stage.

What a stage means (open, won or lost) comes from the pipeline stage's `is_won` and `is_lost` flags.

---

## List Deals

`GET /deals` · Scope: `deals:read`

Returns Deals newest first.

### 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/deals?limit=100" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
const res = await fetch("https://crm.empiraa.app/api/v1/deals?limit=100", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const { data, meta } = await res.json();
```

### Response `200`

```json
{
  "data": [
    {
      "id": "7c2e9f14-3a6b-4d1e-8f5a-9b0c2d4e6f81",
      "name": "Contoso renewal 2027",
      "value": 48500,
      "value_period": "yearly",
      "probability": 60,
      "expected_close_at": "2026-12-15T00:00:00.000Z",
      "actual_close_at": null,
      "stage_id": "rds_proposal",
      "account_id": "3f6c2a1e-8b4d-4c2a-9f1e-2b7d5a9c4e10",
      "owner_id": null,
      "pipeline_id": null,
      "created_at": "2026-11-12T09:30:00.000Z",
      "updated_at": "2026-11-12T09:30:00.000Z"
    }
  ],
  "meta": { "cursor": null, "hasMore": false },
  "error": null
}
```

---

## Create a Deal

`POST /deals` · Scope: `deals:write` · Supports [`Idempotency-Key`](../conventions.md#idempotency)

### Request body

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `name` | string | Yes | |
| `account_id` | string | Yes | An Account in this workspace. |
| `stage_id` | string | Yes | A deal stage ID in the Deal's pipeline. See [Stage IDs](#stage-ids). |
| `pipeline_id` | string or null | No | A pipeline in this workspace. Leave out for the default pipeline. |
| `value` | number or null | No | |
| `value_period` | string | No | `once_off` (default), `monthly`, `quarterly` or `yearly`. |
| `probability` | integer | No | 0 to 100. Default 50. |
| `expected_close_at` | string or null | No | ISO 8601 date or timestamp. |
| `owner_id` | string or null | No | A user ID from `GET /users`. |

### Example

```bash
curl -X POST https://crm.empiraa.app/api/v1/deals \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8b0f6c1e-2d4a-4f7b-9c3e-5a1d7e9b2c40" \
  -d '{
    "name": "Contoso renewal 2027",
    "account_id": "3f6c2a1e-8b4d-4c2a-9f1e-2b7d5a9c4e10",
    "stage_id": "rds_proposal",
    "value": 48500,
    "value_period": "yearly",
    "expected_close_at": "2026-12-15"
  }'
```

```js
const res = await fetch("https://crm.empiraa.app/api/v1/deals", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    name: "Contoso renewal 2027",
    account_id: "3f6c2a1e-8b4d-4c2a-9f1e-2b7d5a9c4e10",
    stage_id: "rds_proposal",
    value: 48500,
    value_period: "yearly",
    expected_close_at: "2026-12-15",
  }),
});
```

### Response `201`

The new [Deal object](#the-deal-object) in `data`.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `INVALID_REQUEST` | `name`, `stage_id` or `account_id` is missing; `pipeline_id` isn't a string; or `expected_close_at` isn't a date. |
| 404 | `NOT_FOUND` | The Account or pipeline doesn't exist in this workspace, or `owner_id` isn't an active member of the workspace. |
| 422 | `INVALID_STAGE` | `stage_id` isn't a stage of the Deal's pipeline. |

**Webhook events:** [`deal.created`](../webhooks.md#event-catalog).

---

## Get a Deal

`GET /deals/{id}` · Scope: `deals:read`

### Path parameters

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

### Example

```bash
curl https://crm.empiraa.app/api/v1/deals/7c2e9f14-3a6b-4d1e-8f5a-9b0c2d4e6f81 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
await fetch("https://crm.empiraa.app/api/v1/deals/7c2e9f14-3a6b-4d1e-8f5a-9b0c2d4e6f81", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
```

### Response `200`

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

### Errors

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

---

## Update a Deal

`PATCH /deals/{id}` · Scope: `deals:write`

Send only the fields you want to change.

### Request body

| Field | Type | Notes |
| --- | --- | --- |
| `name` | string | |
| `value` | number or null | |
| `probability` | integer | 0 to 100. |
| `stage_id` | string | A deal stage ID in the Deal's pipeline. See [Stage IDs](#stage-ids). |
| `expected_close_at` | string or null | ISO 8601 date or timestamp. |

Other fields, including `owner_id`, `value_period`, `pipeline_id` and `account_id`, can't be changed through this endpoint and are ignored.

### Example: move to a new stage

```bash
curl -X PATCH https://crm.empiraa.app/api/v1/deals/7c2e9f14-3a6b-4d1e-8f5a-9b0c2d4e6f81 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"stage_id": "rds_negotiation", "probability": 80}'
```

```js
await fetch("https://crm.empiraa.app/api/v1/deals/7c2e9f14-3a6b-4d1e-8f5a-9b0c2d4e6f81", {
  method: "PATCH",
  headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ stage_id: "rds_negotiation", probability: 80 }),
});
```

### Response `200`

The updated [Deal object](#the-deal-object) in `data`.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `INVALID_REQUEST` | `expected_close_at` isn't a date. |
| 404 | `NOT_FOUND` | No Deal with that ID in this workspace. |
| 422 | `INVALID_STAGE` | `stage_id` isn't a stage of the Deal's pipeline. |

**Webhook events:** [`deal.won`](../webhooks.md#event-catalog) when this request raises `probability` to 100 or more from below 100. Changing `stage_id` alone doesn't send `deal.won`.

---

## Delete a Deal

`DELETE /deals/{id}` · Scope: `deals:write`

Deletes the Deal permanently. This can't be undone.

### Example

```bash
curl -X DELETE https://crm.empiraa.app/api/v1/deals/7c2e9f14-3a6b-4d1e-8f5a-9b0c2d4e6f81 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
await fetch("https://crm.empiraa.app/api/v1/deals/7c2e9f14-3a6b-4d1e-8f5a-9b0c2d4e6f81", {
  method: "DELETE",
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
```

### Response `204`

No body.

### Errors

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

**Webhook events:** none.
