Skip to main content
Staffify Docs
Updated

Deals

A deal (crm_deals) represents a sales opportunity moving through a pipeline. Scopes: crm.deals.read / crm.deals.write.

Endpoints

GET/api/workspace/v1/deals
GET/api/workspace/v1/deals/{id}
POST/api/workspace/v1/deals
PATCH/api/workspace/v1/deals/{id}

The deal object

FieldTypeDescription
name*stringDeal name (required on create)
valuenumber | stringNon-negative deal value
currencystring(3)ISO 4217 e.g. EUR (default from workspace settings)
stagestringPipeline stage name. Must exist in the deal's pipeline.
pipeline_idintegerOn create only. cannot be moved via PATCH.
close_datestring(ISO)Expected close date
probabilityinteger0-100 win probability
crm_contact_idintegerPrimary contact id. Only the primary contact enters deal-stage automations.
crm_company_idintegerLinked CRM company id
owner_user_idintegerTeam member owning the deal
sourceenumai_call | manual | import
is_archivedbooleanPATCH true to soft-delete
custom_fieldsobjectMerged on update
additional_contactsarrayCreate 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"].

GET/api/workspace/v1/deals/{id}/contacts
POST/api/workspace/v1/deals/{id}/contacts
PATCH/api/workspace/v1/deals/{id}/contacts/{contact_id}
DELETE/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] }'
Deals - Workspace API - Staffify