# Deals

CRM deals and the pipelines they move through, including values, stages and write-back.

Source: https://sourceloop.ai/help/api/deals/

---

| Endpoint | Method | Path | Permission |
| --- | --- | --- | --- |
| List deals | GET | `/v1/deals` | `deals:read` |
| List pipelines | GET | `/v1/pipelines` | `deals:read` |
| Get deal | GET | `/v1/deals/{id}` | `deals:read` |
| Update deal | PATCH | `/v1/deals/{id}` | `deals:write` |
| Get deal journey | GET | `/v1/deals/{id}/journey` | `deals:read` |

Base URL: `https://app.sourceloop.ai/api/v1`

## List deals

`GET /v1/deals`

Your pipeline, with the attribution we computed for it

Requires scope: `deals:read`

Deals mirrored from your CRM, each carrying the first-touch and last-touch
attribution Sourceloop derived by walking the touchpoints of everyone linked
to the deal. Your CRM does not hold those fields, which is the reason to read
deals here rather than from the CRM API.

Every row includes `stage.normalized_bucket` (open, won, lost, other). Stage
names are chosen by the customer, so a report that trusts the label breaks the
day someone renames a stage.

Returns a connection-required error, not an empty list, when no CRM is
connected. "No deals" and "no CRM" are different answers.

### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. |
| `status` | string | no |  |
| `pipeline_id` | string | no |  |
| `stage_id` | string | no |  |
| `account_id` | string | no |  |
| `owner_email` | string | no |  |
| `min_amount` | number | no |  |
| `closed_after` | string | no |  |
| `closed_before` | string | no |  |
| `channel` | string | no | First-touch channel that introduced the company. |
| `source` | string | no | First-touch source. |
| `campaign` | string | no | First-touch campaign. |
| `latest_channel` | string | no |  |
| `sort` | string | no |  |
| `cursor` | string | no |  |
| `limit` | integer | no |  |
| `total_count` | boolean | no | Include meta.total_count, the exact number of matching rows. Off by default because counting scans every match while the page itself reads one page, so on a large workspace the count costs far more than the rows. Paginate with has_more and next_cursor unless you are rendering "70 of 366". |

### Responses

- `200` Deals with attribution
- `400` Something was wrong with the request or the credential.
- `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged.

### Example (cURL)

```bash
curl -s -X GET "https://app.sourceloop.ai/api/v1/deals?website=acme.com" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
```

### Example (Node)

```javascript
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/deals?website=acme.com",
  {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
    },
  },
);

const data = await res.json();
```

### Example (Python)

```python
import os, requests

res = requests.get(
    "https://app.sourceloop.ai/api/v1/deals?website=acme.com",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json()
```

## List pipelines

`GET /v1/pipelines`

Pipelines and their stages, in order

Requires scope: `deals:read`

Needed to interpret a deal's stage: which stages exist, what order they run in, their win probability, and which of the customer's stage names actually mean won.

### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. |

### Responses

- `200` Pipelines with stages
- `400` Something was wrong with the request or the credential.
- `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged.

### Example (cURL)

```bash
curl -s -X GET "https://app.sourceloop.ai/api/v1/pipelines?website=acme.com" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
```

### Example (Node)

```javascript
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/pipelines?website=acme.com",
  {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
    },
  },
);

const data = await res.json();
```

### Example (Python)

```python
import os, requests

res = requests.get(
    "https://app.sourceloop.ai/api/v1/pipelines?website=acme.com",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json()
```

## Get deal

`GET /v1/deals/{id}`

One deal, with its people and its history

Requires scope: `deals:read`

Adds the contacts on the deal, the append-only stage history (which survives a stage being renamed later, so time-in-stage stays computable) and the pipeline it belongs to.

### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |

### Responses

- `200` Deal detail
- `404` Something was wrong with the request or the credential.
- `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged.

### Example (cURL)

```bash
curl -s -X GET "https://app.sourceloop.ai/api/v1/deals/{id}" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
```

### Example (Node)

```javascript
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/deals/{id}",
  {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
    },
  },
);

const data = await res.json();
```

### Example (Python)

```python
import os, requests

res = requests.get(
    "https://app.sourceloop.ai/api/v1/deals/{id}",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json()
```

## Update deal

`PATCH /v1/deals/{id}`

Move a deal, or change its value, in your CRM

Requires scope: `deals:write`

Your CRM owns every field on a deal, so this does not write a value that lives
only here: the change is saved and queued for push back to HubSpot or Pipedrive.
Check `push.queued` in the response. When it is false the value was saved but
NOT sent (outbound disabled, or the connection is not active) and the next sync
from the CRM will overwrite it.

`status` is translated for you. HubSpot has no writable status field, because won
and lost are stages there, so {"status":"won"} is resolved to that pipeline's won
stage and `notes` reports which. Pipedrive accepts status directly. If the pipeline
has no matching stage, HubSpot returns 422 asking for a stage_id from /pipelines.

Send an `Idempotency-Key`. A retry returns the original result rather than pushing
to the CRM twice, and a request that changes nothing writes and pushes nothing.

`expected_revenue` is not writable: both providers compute it from amount and stage
probability. Attribution is computed by Sourceloop and cannot be set.

### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |

### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | no | A unique token per distinct request. Strongly recommended: this writes to your CRM. |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no |  |
| `amount` | number | no |  |
| `currency` | string | no |  |
| `close_date` | string | no |  |
| `status` | string | no |  |
| `stage_id` | string | no | From GET /pipelines. Takes precedence over status. |

### Responses

- `200` Updated and queued for push, or unchanged
- `403` Something was wrong with the request or the credential.
- `404` Something was wrong with the request or the credential.
- `422` Something was wrong with the request or the credential.
- `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged.

### Example (cURL)

```bash
curl -s -X PATCH "https://app.sourceloop.ai/api/v1/deals/{id}" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "won",
    "amount": 14000
  }'
```

### Example (Node)

```javascript
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/deals/{id}",
  {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "status": "won",
      "amount": 14000
    }),
  },
);

const data = await res.json();
```

### Example (Python)

```python
import os, requests

res = requests.patch(
    "https://app.sourceloop.ai/api/v1/deals/{id}",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
    json={
      "status": "won",
      "amount": 14000
    },
)
data = res.json()
```

## Get deal journey

`GET /v1/deals/{id}/journey`

The marketing behind one deal

Requires scope: `deals:read`

Every session by every contact attached to the deal. The closed loop stated in full: not "paid search influenced 60,000 of pipeline", but the sessions that claim actually rests on.

### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |

### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `days` | integer | no |  |

### Responses

- `200` Journey
- `404` Something was wrong with the request or the credential.
- `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged.

### Example (cURL)

```bash
curl -s -X GET "https://app.sourceloop.ai/api/v1/deals/{id}/journey?days=365" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
```

### Example (Node)

```javascript
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/deals/{id}/journey?days=365",
  {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
    },
  },
);

const data = await res.json();
```

### Example (Python)

```python
import os, requests

res = requests.get(
    "https://app.sourceloop.ai/api/v1/deals/{id}/journey?days=365",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json()
```
