Deals & Pipelines
Model your sales process as pipelines and stages, then move deals through them — with automatic won/lost status and close stamping.
Overview
A pipeline is one sales process. Its stages are the ordered steps, each with a position, an optional win probability, and flags marking it as the won or lost terminus. A deal sits in exactly one stage of one pipeline.
Moving a deal onto a stage flagged is_won or is_lost sets the deal's status and stamps closed_at for you — you do not manage those by hand.
Authentication
Authorization: Bearer cm_your_api_keyPipelines and deals share one scope pair.
| Operation | Scope |
|---|---|
| Read pipelines, stages, deals | crm_deals:read |
| Create, update, move, delete | crm_deals:write |
Pipelines
{
"id": "11aa…",
"tenant_id": "a0b1…",
"name": "Inbound",
"is_default": true,
"created_at": "2026-08-01T09:00:00Z",
"updated_at": "2026-08-01T09:00:00Z"
}Exactly one pipeline may be the default. Setting is_default: true clears the flag on the others in the same transaction.
| Method | Path | Notes |
|---|---|---|
GET | /api/v1/crm/pipelines | Default first, then oldest first. limit 1..200 (default 50), offset 0..100000 |
POST | /api/v1/crm/pipelines | 201. name 1–255 characters, unique per tenant (409 A pipeline with this name already exists); is_default default false |
GET | /api/v1/crm/pipelines/{pipeline_id} | |
PATCH | /api/v1/crm/pipelines/{pipeline_id} | name, is_default |
DELETE | /api/v1/crm/pipelines/{pipeline_id} | 204, and its stages are deleted with it. 409 Move or delete the deals here first if it still holds deals |
Stages
{
"id": "22bb…",
"tenant_id": "a0b1…",
"pipeline_id": "11aa…",
"name": "Negotiation",
"position": 3,
"probability": 60,
"is_won": false,
"is_lost": false,
"created_at": "2026-08-01T09:01:00Z",
"updated_at": "2026-08-01T09:01:00Z"
}GET /api/v1/crm/pipelines/{pipeline_id}/stages
Ordered by position. No pagination — a pipeline's stage list is always returned whole.
POST /api/v1/crm/pipelines/{pipeline_id}/stages
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | Yes | 1–255 characters, not blank |
position | integer | No | 0 <= position <= 10000. Omit to append after the current last stage |
probability | number | No | 0 <= probability <= 100, as a percentage |
is_won | boolean | No | Default false |
is_lost | boolean | No | Default false |
A stage cannot be both — 422 A stage cannot be both won and lost. Returns 201.
curl -X POST https://api.callmissed.com/api/v1/crm/pipelines/11aa…/stages \
-H "Authorization: Bearer cm_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Won", "probability": 100, "is_won": true }'PATCH /api/v1/crm/pipelines/stages/{stage_id} · DELETE /api/v1/crm/pipelines/stages/{stage_id}
PATCH accepts name, position, probability, is_won and is_lost, all optional, with the same bounds. The won/lost rule is checked against the merged result. Changing a stage's flags does not re-close or re-open deals already on it.
DELETE returns 204, or 409 Move or delete the deals here first when deals still sit on it.
PATCH /api/v1/crm/pipelines/{pipeline_id}/stages/reorder
| Field | Type | Required | Constraints |
|---|---|---|---|
stage_ids | UUID[] | Yes | 1–100 entries. Must be every stage of this pipeline, exactly once |
New position is the array index. Returns the stages in their new order.
A list with repeats returns 422 stage_ids contains duplicates; a list that misses a stage or includes one from another pipeline returns 422 stage_ids must list every stage in this pipeline, exactly once. Either way nothing changes.
Deals
The deal object
{
"id": "33cc…",
"tenant_id": "a0b1…",
"title": "Acme — 3 voice agents",
"value": 240000.0,
"currency": "INR",
"pipeline_id": "11aa…",
"stage_id": "22bb…",
"contact_id": "4411…",
"company_id": "5c6d…",
"owner_user_id": "b1f2…",
"status": "open",
"expected_close_date": "2026-09-15",
"closed_at": null,
"lost_reason": null,
"last_activity_at": "2026-08-16T12:05:00Z",
"created_at": "2026-08-05T09:00:00Z",
"updated_at": "2026-08-16T12:05:00Z"
}| Field | Type | Notes |
|---|---|---|
status | string | open, won or lost. Driven by the stage — see below |
currency | string | ISO 4217, three letters, upper-cased. Default INR |
closed_at | datetime | null | Stamped when the deal lands on a won or lost stage. Moving between won and lost keeps the original time; re-opening clears it |
lost_reason | string | null | Why the deal was lost, up to 255 characters. Kept only while status is lost; cleared when the deal is re-opened or won |
last_activity_at | datetime | null | Set on create and bumped on every update or move |
GET /api/v1/crm/deals
Newest first.
| Parameter | Type | Constraints |
|---|---|---|
pipeline_id | UUID | |
stage_id | UUID | |
status | string | open, won or lost. Anything else is 422 |
owner_user_id | UUID | |
contact_id | UUID | |
company_id | UUID | |
limit | integer | 1 <= limit <= 200, default 50 |
offset | integer | 0 <= offset <= 100000, default 0 |
GET /api/v1/crm/deals/summary
Board totals for one pipeline, over every deal rather than one page: the count, value and weighted value of each stage (in board order, empty stages included) and of the whole pipeline.
| Parameter | Type | Constraints |
|---|---|---|
pipeline_id | UUID | Required. Must be your pipeline, otherwise 404 |
status | string | open, won or lost. Anything else is 422 |
owner_user_id | UUID | |
contact_id | UUID | |
company_id | UUID |
weighted_value is value × probability / 100 using the deal's stage probability. A stage with no probability counts at 100 when it is a won stage and 0 otherwise. Amounts in different currencies are never added together — totals has one entry per currency, sorted by currency.
{
"pipeline_id": "11aa…",
"count": 14,
"totals": [{ "currency": "INR", "value": 2400000.0, "weighted_value": 610000.0 }],
"stages": [
{
"stage_id": "22bb…",
"count": 6,
"totals": [{ "currency": "INR", "value": 900000.0, "weighted_value": 180000.0 }]
}
]
}POST /api/v1/crm/deals
| Field | Type | Required | Constraints |
|---|---|---|---|
title | string | Yes | 1–255 characters, not blank |
pipeline_id | UUID | Yes | Must be your pipeline |
stage_id | UUID | No | Must belong to that pipeline. Omit to start at the lowest-position stage |
value | number | No | 0 <= value <= 999999999999.99 |
currency | string | No | Exactly 3 letters, default INR |
contact_id | UUID | No | |
company_id | UUID | No | |
owner_user_id | UUID | No | Must be a user in your tenant |
expected_close_date | date | No | |
lost_reason | string | No | Up to 255 characters. Kept only if the deal starts on a lost stage |
curl -X POST https://api.callmissed.com/api/v1/crm/deals \
-H "Authorization: Bearer cm_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Acme — 3 voice agents",
"pipeline_id": "11aa…",
"value": 240000,
"company_id": "5c6d…",
"expected_close_date": "2026-09-15"
}'Returns 201. Creating straight onto a won or lost stage closes the deal immediately.
Creating in a pipeline that has no stages yet returns 422 This pipeline has no stages yet — add stages first.
POST /api/v1/crm/deals/{deal_id}/move
| Field | Type | Required |
|---|---|---|
stage_id | UUID | Yes |
lost_reason | string | No. Up to 255 characters; recorded only when the stage is flagged is_lost |
The one call you want for a kanban drag. The stage must belong to the deal's pipeline — 422 Stage does not belong to this pipeline otherwise.
curl -X POST https://api.callmissed.com/api/v1/crm/deals/33cc…/move \
-H "Authorization: Bearer cm_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "stage_id": "22bb…" }'Returns the updated deal.
What a move does to status
| Destination stage | Effect |
|---|---|
is_won | status becomes won, closed_at stamped if unset |
is_lost | status becomes lost, closed_at stamped if unset |
| Neither | status returns to open, closed_at and lost_reason are cleared |
So re-opening a deal is just moving it back to a working stage.
GET / PATCH / DELETE /api/v1/crm/deals/{deal_id}
PATCH accepts title, value, currency, contact_id, company_id, owner_user_id, expected_close_date, stage_id, status and lost_reason, all optional, with the same bounds as create. pipeline_id cannot be changed.
stage_idfollows the same rules as a move: it must belong to the deal's pipeline, and it setsstatusandclosed_atas in the table above.status(open,wonorlost) marks the deal closed or open without moving it. Sent together withstage_id, the explicitstatuswins.won/loststampsclosed_atif unset;openclears it.
DELETE returns 204.
Errors
| Status | When |
|---|---|
403 | Key is missing crm_deals:read / crm_deals:write |
404 | Deal, pipeline, stage, contact, company or owner not in your tenant |
409 | Duplicate pipeline name, or a pipeline/stage that still holds deals |
422 | Blank name/title, a stage outside the deal's pipeline, a stage flagged both won and lost, an incomplete reorder list, or an empty pipeline |
Nothing on this page consumes credits.