Deals
A deal (crm_deals) represents a sales opportunity moving through a pipeline. Scopes: crm.deals.read / crm.deals.write.
Endpoints
/api/workspace/v1/deals/api/workspace/v1/deals/{id}/api/workspace/v1/deals/api/workspace/v1/deals/{id}The deal object
| Field | Type | Description |
|---|---|---|
| name* | string | Deal name (required on create) |
| value | number | string | Non-negative deal value |
| currency | string(3) | ISO 4217 e.g. EUR (default from workspace settings) |
| stage | string | Pipeline stage name. Must exist in the deal's pipeline. |
| pipeline_id | integer | On create only. cannot be moved via PATCH. |
| close_date | string(ISO) | Expected close date |
| probability | integer | 0-100 win probability |
| crm_contact_id | integer | Primary contact id. Only the primary contact enters deal-stage automations. |
| crm_company_id | integer | Linked CRM company id |
| owner_user_id | integer | Team member owning the deal |
| source | enum | ai_call | manual | import |
| is_archived | boolean | PATCH true to soft-delete |
| custom_fields | object | Merged on update |
| additional_contacts | array | Create only. Extra contacts [{ contact_id, role? }], max 50. Manage later via /contacts. |
Create a deal
curl "https://app.staffifyai.com/api/workspace/v1/deals" \
-X POST \
-H "Authorization: Bearer sfy_wsp_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q4 renewal. Acme",
"value": 12000,
"currency": "EUR",
"stage": "Qualified",
"crm_contact_id": 1893,
"crm_company_id": 42
}'List deals + kanban mode
Standard list with page, limit plus filters: stage, owner, contact_id, pipeline_id,archived.
For a kanban view use group_by_stage=true. we return up to per_stage_limit deals per stage plus per_stage_totals so you can render "and N more" labels.
curl "https://app.staffifyai.com/api/workspace/v1/deals?pipeline_id=3&group_by_stage=true&per_stage_limit=10" \ -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY"
Move a deal between stages
PATCH the stage. We validate the stage exists in the deal's current pipeline. A stage-change also fires the deal.stage_changed webhook. see Webhooks.
curl "https://app.staffifyai.com/api/workspace/v1/deals/1024" \
-X PATCH \
-H "Authorization: Bearer sfy_wsp_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "stage": "Won" }'Extra contacts
A deal has one primary contact (crm_contact_id) and up to 50 extra contacts, each with an optional role: decision_maker, financial, technical or other. Extra contacts never enter deal-stage automations; only the primary contact does. The primary contact can't also be an extra contact (400), and when you PATCH an extra contact to become crm_contact_id it is removed from the extra list. Adding a contact that is already linked only updates its role (200 instead of 201). Unlinking never deletes the contact itself, so it uses the crm.deals.write scope. Every change fires deal.updated with updated_fields: ["contacts"].
/api/workspace/v1/deals/{id}/contacts/api/workspace/v1/deals/{id}/contacts/api/workspace/v1/deals/{id}/contacts/{contact_id}/api/workspace/v1/deals/{id}/contacts/{contact_id}# Link a contact as decision maker
curl "https://app.staffifyai.com/api/workspace/v1/deals/1024/contacts" \
-X POST \
-H "Authorization: Bearer sfy_wsp_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "contact_id": 1894, "role": "decision_maker" }'
# Response
{
"object": "deal_contact",
"deal_id": 1024,
"contact_id": 1894,
"role": "decision_maker",
"name": "Piet Bakker",
"email": "[email protected]",
"phone": null,
"job_title": "Director",
"crm_company_id": 42,
"created_at": "2026-10-26T10:12:00.000Z"
}Pipeline changes
pipeline_id can only be set at create time. To move a deal to a different pipeline, create a new deal or use the workspace UI (auditable). This restriction protects your funnel analytics.
Delete / restore / bulk-delete
Soft-delete places a deal in a 30-day recycle bin. Deleted deals are excluded from default list responses (including group_by_stage kanban mode) and don't contribute to per_stage_totals. Restore within the window via POST /:id/restore. Requires the separate crm.deals.delete scope.
# Soft-delete
curl -X DELETE "https://app.staffifyai.com/api/workspace/v1/deals/1024" \
-H "Authorization: Bearer sfy_wsp_live_YOUR_KEY"
# Restore
curl -X POST "https://app.staffifyai.com/api/workspace/v1/deals/1024/restore" \
-H "Authorization: Bearer sfy_wsp_live_YOUR_KEY"
# Bulk-delete (max 500 ids)
curl -X POST "https://app.staffifyai.com/api/workspace/v1/deals/bulk-delete" \
-H "Authorization: Bearer sfy_wsp_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "ids": [1024, 1025] }'