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.
Contents
- What a deal is
- Create a deal
- Read and update a deal
- List deals
- Activities on a deal
- Fields
- Reports
- The figures in detail
- 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.
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.
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:
{
"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
valueis missing, the deal takes the lead's estimated value (est_value). Ifconfidenceis 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
200with"status": "duplicate"and the existing deal. So simply checkstatusinstead 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.
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 }'
stageswitches the stage.wonsetswon_at,lostsetslost_atto now. inDeal writes the history of stage changes itself.- With
lost,lost_reasonis worth adding - the reason shows in the app and in the webhook eventdeal.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 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; 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.
curl "https://app.indeal.ai/api/v1/reports/summary?from=2026-09-01&to=2026-09-30§ions=deals,conversion" \
-H "Authorization: Bearer indeal_..."
Response with status 200, here with all four blocks:
{
"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.