
With the inDeal API you create deals for your leads, move them through the stages to closing and fetch ready-made figures without paging through lists yourself. Key, base URL and basics are the same as for leads: [inDeal API: create and update leads via the interface](https://success.indeal.ai/hc/indeal/articles/indeal-api-create-and-update-leads).

**Contents**
- [What a deal is](#what-a-deal-is)
- [Create a deal](#create-a-deal)
- [Read and update a deal](#read-and-update-a-deal)
- [List deals](#list-deals)
- [Activities on a deal](#activities-on-a-deal)
- [Fields](#fields)
- [Reports](#reports)
- [The figures in detail](#the-figures-in-detail)
- [Next](#next)


## What a deal is

A deal comes from a lead and keeps its origin in `lead_id`. It moves through the stages `send_offer` and `order_received` and ends in `won` or `lost`. Each lead has at most one open deal - the same rule as in the app, see [From lead to deal and back](https://success.indeal.ai/hc/indeal/articles/from-lead-to-deal-and-back).

## Create a deal

`POST /deals` creates a deal. `lead_id` is required; the lead has to exist in your account, otherwise the API responds with `404`.

```bash
curl -X POST https://app.indeal.ai/api/v1/deals \
  -H "Authorization: Bearer indeal_..." \
  -H "Content-Type: application/json" \
  -d '{
    "lead_id": "0b0e...",
    "value": 12000,
    "confidence": 40,
    "expected_close_date": "2026-10-15"
  }'
```

Response with status `201`:

```json
{
  "status": "created",
  "deal": {
    "id": "7c1d...",
    "lead_id": "0b0e...",
    "stage": "send_offer",
    "value": 12000,
    "confidence": 40,
    "weighted_value": 4800,
    "expected_close_date": "2026-10-15",
    "won_at": null,
    "lost_at": null,
    "url": "https://app.indeal.ai/pipeline/7c1d...",
    "lead": {
      "id": "0b0e...",
      "full_name": "Julia Weber",
      "company_name": "Muster GmbH",
      "email": "julia.weber@muster.de",
      "url": "https://app.indeal.ai/leads/0b0e..."
    }
  }
}
```

Three things happen automatically:

- If `value` is missing, the deal takes the lead's estimated value (`est_value`). If `confidence` is missing, it is 25.
- The lead now counts as converted. Its stage stays unchanged.
- If the lead already has a deal that is not in the trash, no second one is created. You get status `200` with `"status": "duplicate"` and the existing deal. So simply check `status` instead of looking it up first.

## Read and update a deal

`GET /deals/{id}` returns `{ "deal": { ... } }`. An unknown id or a deal from another account results in `404`.

`PATCH /deals/{id}` changes only the fields you send. `null` empties a field.

```bash
curl -X PATCH https://app.indeal.ai/api/v1/deals/7c1d... \
  -H "Authorization: Bearer indeal_..." \
  -H "Content-Type: application/json" \
  -d '{ "stage": "won", "value": 15000 }'
```

- `stage` switches the stage. `won` sets `won_at`, `lost` sets `lost_at` to now. inDeal writes the history of stage changes itself.
- With `lost`, `lost_reason` is worth adding - the reason shows in the app and in the webhook event `deal.lost`.
- If you change `next_step_due`, a reminder set in the app for that date is dropped.

## List deals

`GET /deals` returns deals page by page, sorted by last change.

| Parameter | Meaning |
|---|---|
| `stage` | only this stage |
| `lead_id` | only deals of this lead |
| `updated_since` | ISO 8601, only deals changed since then |
| `order` | `asc` (default, oldest first) or `desc` |
| `limit` | 1 to 200, default 100 |
| `cursor` | `next_cursor` from the previous response |

Response: `{ "deals": [...], "next_cursor": "..." }`. If `next_cursor` is `null`, that was the last page. For totals and counts you do not need this list - that is what [Reports](#reports) are for.

## Activities on a deal

Deals have their own timeline. `POST /deals/{id}/activities` and `GET /deals/{id}/activities` work exactly like activities on a lead: the same types (`note`, `call`, `email`, `meeting_scheduled`), the same fields, the same paging. An entry on a deal belongs to the deal only, not additionally to the lead.

## Fields

| Field | Type | Note |
|---|---|---|
| `id`, `created_at`, `updated_at`, `stage_entered_at` | read-only | |
| `lead_id` | id | originating lead, required on create |
| `stage` | choice | `send_offer`, `order_received`, `won`, `lost` |
| `value` | number | deal volume |
| `confidence` | integer 0 to 100 | probability in percent |
| `weighted_value` | read-only | `value` times `confidence` divided by 100 |
| `expected_close_date` | date `YYYY-MM-DD` | expected closing |
| `offer_sent_at`, `next_step_due` | ISO 8601 | |
| `next_step`, `notes`, `lost_reason` | text | `lost_reason` only on update |
| `won_at`, `lost_at` | read-only | set when switching to `won` or `lost` |
| `url` | read-only | link to the deal page in inDeal |
| `lead` | read-only | `id`, `full_name`, `company_name`, `email` and `url` of the lead |
| `custom_data` | object | custom fields for deals, same format and checks as for leads, see [Custom fields](https://success.indeal.ai/hc/indeal/articles/indeal-api-create-and-update-leads#custom-fields); `GET /custom-fields?entity=deal` lists the fields |

Not reachable via the API: the assignment to a team member, reminders and the trash.

## Reports

`GET /reports/summary` returns ready-made figures for a period - leads, meetings, deals and conversion in one response. The figures cover the whole account, regardless of who a lead is assigned to. All times are UTC.

| Parameter | Meaning |
|---|---|
| `from` | start, `YYYY-MM-DD` or ISO 8601. Default: the 1st of the current month |
| `to` | end, `YYYY-MM-DD` (that day counts) or ISO 8601 (exclusive). Default: now |
| `sections` | comma-separated from `leads`, `meetings`, `deals`, `conversion`. Default: all four |

The period may be at most 366 days long and `from` has to be before `to` - otherwise the API responds with `400`.

```bash
curl "https://app.indeal.ai/api/v1/reports/summary?from=2026-09-01&to=2026-09-30&sections=deals,conversion" \
  -H "Authorization: Bearer indeal_..."
```

Response with status `200`, here with all four blocks:

```json
{
  "period": { "from": "2026-09-01T00:00:00+00:00", "to": "2026-10-01T00:00:00+00:00" },
  "leads": {
    "created": 12,
    "by_stage": [ { "stage": "qualifying", "count": 40, "est_value_sum": 380000 } ],
    "by_source_tool": [ { "source_tool": "api", "count": 12 } ],
    "by_lead_source": [ { "lead_source": "Webinar", "count": 7 } ]
  },
  "meetings": { "scheduled": 5, "done": 3 },
  "deals": {
    "created": 3,
    "by_stage": [ { "stage": "send_offer", "count": 4, "value_sum": 54000, "weighted_sum": 21600 } ],
    "pipeline_value": 54000,
    "pipeline_weighted": 21600,
    "won_count": 1,
    "won_value": 15000,
    "lost_count": 1,
    "avg_days_to_won": 12.5
  },
  "conversion": {
    "leads_created": 12,
    "meetings_scheduled": 5,
    "deals_created": 3,
    "deals_won": 1,
    "meeting_rate": 0.4167,
    "deal_rate": 0.6,
    "win_rate": 0.3333
  }
}
```

Rates are numbers between 0 and 1 with four decimals. Leads and deals in the trash never count.

## The figures in detail

The response holds two kinds of numbers: counters for the period (what happened between `from` and `to`) and snapshot figures of today (what the pipeline looks like right now). `by_stage`, `pipeline_value` and `pipeline_weighted` are snapshot figures, everything else refers to the period.

| Figure | Meaning |
|---|---|
| `period.from`, `period.to` | the evaluated period, `from` inclusive, `to` exclusive |
| `leads.created` | leads created in the period |
| `leads.by_stage` | all current leads per stage with count and sum of the estimated value |
| `leads.by_source_tool` | leads created in the period per source (for example `api`, `import`, `extension`) |
| `leads.by_lead_source` | leads created in the period per origin from the lead source field, the 20 most frequent; empty means `unknown` |
| `meetings.scheduled` | leads moved to meeting scheduled in the period |
| `meetings.done` | leads moved to meeting done in the period |
| `deals.created` | deals created in the period |
| `deals.by_stage` | all current deals per stage with count, sum of values and weighted sum |
| `deals.pipeline_value` | sum of the values of all open deals (send offer, order received), as of today |
| `deals.pipeline_weighted` | the same sum, weighted by confidence |
| `deals.won_count`, `deals.won_value` | deals won in the period, count and sum of values |
| `deals.lost_count` | deals lost in the period |
| `deals.avg_days_to_won` | average days from creating the deal to winning it, only for deals won in the period; `null` without won deals |
| `conversion.leads_created` | same as `leads.created` |
| `conversion.meetings_scheduled` | same as `meetings.scheduled` |
| `conversion.deals_created` | same as `deals.created` |
| `conversion.deals_won` | same as `deals.won_count` |
| `conversion.meeting_rate` | meetings scheduled divided by leads created; `null` without leads |
| `conversion.deal_rate` | deals created divided by meetings scheduled; `null` without meetings |
| `conversion.win_rate` | deals won divided by deals created; `null` without deals |

The rates in `conversion` use the counters of the same period. They are therefore not a true funnel of one cohort: a meeting in September can belong to a lead from August.

## Next

- [inDeal API: create and update leads via the interface](https://success.indeal.ai/hc/indeal/articles/indeal-api-create-and-update-leads)
- [inDeal Webhooks: send lead and deal changes to other systems](https://success.indeal.ai/hc/indeal/articles/indeal-webhooks-send-lead-changes)
- [MCP: Connect Claude, ChatGPT and Cursor](https://success.indeal.ai/hc/indeal/articles/connect-ai-assistants-to-indeal)
