SendSetsDocs
Endpoint reference

CRM

Manage pipelines, stages, deals, task types, and CRM tasks for your organization.

The CRM endpoints model your sales pipeline: deals move through stages of a pipeline, and CRM tasks track the follow-up work attached to contacts and deals. Every route is organization-scoped and requires an active organization on the session or API key. Read access uses the CRM read scope (READ_CRM / org permission view_contacts), and mutations use the CRM write scope (WRITE_CRM / org permission manage_contacts), except team membership operations which gate on manage_team. See authentication and permissions for how scopes map to API keys and member roles.

Every list endpoint returns a data array plus the standard pagination envelope with an opaque next_cursor. The simple GET list endpoints use a keyset cursor; the faceted POST .../search endpoints paginate by offset under the hood (their nullable sort columns rule out a keyset cursor) but expose the same opaque next_cursor, and add an exact total.

List pipelines

GET /crm/pipelines

Return every pipeline in the organization, each with its ordered stages.

Auth: Scope READ_CRM · Org permission view_contacts

Response

Returns a bare array of pipeline objects (not wrapped in an envelope).

[
  {
    "id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
    "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
    "name": "Sales",
    "position": 0,
    "stages": [
      {
        "id": "7f3c1a90-2b4d-4e6f-8a01-b2c3d4e5f607",
        "pipeline_id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
        "name": "Lead",
        "color": "#0ea5e9",
        "position": 0,
        "deal_count": 12,
        "created_at": "2026-05-01T09:00:00Z",
        "updated_at": "2026-05-01T09:00:00Z"
      }
    ],
    "created_at": "2026-05-01T09:00:00Z",
    "updated_at": "2026-05-01T09:00:00Z"
  }
]

Create pipeline

POST /crm/pipelines

Create a pipeline, optionally seeding it with an ordered set of stages.

Auth: Scope WRITE_CRM · Org permission manage_contacts

Request body

FieldTypeRequiredDescription
namestringyesPipeline name (1 to 255 characters).
stagesarraynoStages to create with the pipeline, in order.
stages[].namestringyesStage name (1 to 255 characters).
stages[].colorstringyesStage color (hex).
{
  "name": "Sales",
  "stages": [
    { "name": "Lead", "color": "#0ea5e9" },
    { "name": "Qualified", "color": "#8b5cf6" },
    { "name": "Won", "color": "#22c55e" }
  ]
}

Response

201 Created with the created pipeline object (same shape as in the list response, including its stages).

{
  "id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
  "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
  "name": "Sales",
  "position": 0,
  "stages": [
    {
      "id": "7f3c1a90-2b4d-4e6f-8a01-b2c3d4e5f607",
      "pipeline_id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
      "name": "Lead",
      "color": "#0ea5e9",
      "position": 0,
      "created_at": "2026-06-12T10:00:00Z",
      "updated_at": "2026-06-12T10:00:00Z"
    }
  ],
  "created_at": "2026-06-12T10:00:00Z",
  "updated_at": "2026-06-12T10:00:00Z"
}

Get pipeline

GET /crm/pipelines/:id

Fetch a single pipeline with its stages.

Auth: Scope READ_CRM · Org permission view_contacts

ParameterInTypeDescription
idpathuuidPipeline ID.

Response

Returns the pipeline object (same shape as a list item).

{
  "id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
  "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
  "name": "Sales",
  "position": 0,
  "stages": [],
  "created_at": "2026-05-01T09:00:00Z",
  "updated_at": "2026-05-01T09:00:00Z"
}

Update pipeline

PATCH /crm/pipelines/:id

Rename a pipeline. Only the name can be changed here; stages have their own endpoints.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidPipeline ID.

Request body

FieldTypeRequiredDescription
namestringnoNew pipeline name.
{
  "name": "Enterprise Sales"
}

Response

Returns the updated pipeline object.

Delete pipeline

DELETE /crm/pipelines/:id

Delete a pipeline and its stages.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidPipeline ID.

Response

204 No Content.

Create stage

POST /crm/pipelines/:id/stages

Append a stage to a pipeline.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidPipeline ID.

Request body

FieldTypeRequiredDescription
namestringyesStage name (1 to 255 characters).
colorstringyesStage color (hex).
{
  "name": "Negotiation",
  "color": "#f59e0b"
}

Response

201 Created with the created stage object.

{
  "id": "9c0d1e2f-3a4b-4c5d-6e7f-8a9b0c1d2e3f",
  "pipeline_id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
  "name": "Negotiation",
  "color": "#f59e0b",
  "position": 3,
  "created_at": "2026-06-12T10:05:00Z",
  "updated_at": "2026-06-12T10:05:00Z"
}

Update stage

PATCH /crm/pipelines/:id/stages/:stageId

Rename or recolor a stage.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidPipeline ID.
stageIdpathuuidStage ID.

Request body

FieldTypeRequiredDescription
namestringnoNew stage name.
colorstringnoNew stage color (hex).
{
  "name": "Contract Sent",
  "color": "#6366f1"
}

Response

Returns the updated stage object.

Delete stage

DELETE /crm/pipelines/:id/stages/:stageId

Remove a stage from a pipeline.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidPipeline ID.
stageIdpathuuidStage ID.

Response

204 No Content.

List deals

GET /crm/deals

List deals with optional pipeline, stage, and status filters, keyset-paginated.

Auth: Scope READ_CRM · Org permission view_contacts

ParameterInTypeDescription
pipeline_idqueryuuidRestrict to deals in this pipeline.
stage_idqueryuuidRestrict to deals in this stage.
statusquerystringRestrict to open, won, or lost.
cursorquerystringOpaque cursor from a previous page's pagination.next_cursor.
limitqueryintPage size, 1 to 100 (default 50).

Response

A data array of deal objects plus a keyset pagination envelope. List rows may include joined contact and stage objects and a campaign_name.

{
  "data": [
    {
      "id": "1f2e3d4c-5b6a-4789-90ab-cdef01234567",
      "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
      "pipeline_id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
      "stage_id": "7f3c1a90-2b4d-4e6f-8a01-b2c3d4e5f607",
      "contact_id": "aa11bb22-cc33-4dd4-95ee-66ff77008811",
      "name": "Acme renewal",
      "value": 12000,
      "currency": "USD",
      "status": "open",
      "expected_close_date": "2026-07-15T00:00:00Z",
      "assigned_to": "bb22cc33-dd44-4ee5-86ff-770011223344",
      "campaign_id": "cc33dd44-ee55-4ff6-9700-112233445566",
      "source_mailbox_id": "dd44ee55-ff66-4007-8811-223344556677",
      "created_at": "2026-06-01T12:00:00Z",
      "updated_at": "2026-06-10T09:30:00Z",
      "campaign_name": "Q2 outbound"
    }
  ],
  "pagination": {
    "total": null,
    "next_cursor": "c1_b3BhcXVlLWN1cnNvcg",
    "has_more": true
  }
}

Create deal

POST /crm/deals

Create a deal in a pipeline stage, optionally linked to a contact and attributed to a campaign and source mailbox.

Auth: Scope WRITE_CRM · Org permission manage_contacts

Request body

FieldTypeRequiredDescription
pipeline_iduuidyesPipeline the deal belongs to.
stage_iduuidyesInitial stage.
contact_iduuidnoLinked contact.
namestringyesDeal name (1 to 255 characters).
valuenumbernoMonetary value.
currencystringnoISO currency code.
expected_close_datestring (date-time)noExpected close date.
assigned_touuidnoOwner (org member user ID).
campaign_iduuidnoAttributed campaign.
source_mailbox_iduuidnoSending mailbox that produced the originating reply.
{
  "pipeline_id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
  "stage_id": "7f3c1a90-2b4d-4e6f-8a01-b2c3d4e5f607",
  "contact_id": "aa11bb22-cc33-4dd4-95ee-66ff77008811",
  "name": "Acme renewal",
  "value": 12000,
  "currency": "USD",
  "expected_close_date": "2026-07-15T00:00:00Z",
  "assigned_to": "bb22cc33-dd44-4ee5-86ff-770011223344"
}

Response

201 Created with the created deal object. New deals default to status: "open".

{
  "id": "1f2e3d4c-5b6a-4789-90ab-cdef01234567",
  "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
  "pipeline_id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
  "stage_id": "7f3c1a90-2b4d-4e6f-8a01-b2c3d4e5f607",
  "contact_id": "aa11bb22-cc33-4dd4-95ee-66ff77008811",
  "name": "Acme renewal",
  "value": 12000,
  "currency": "USD",
  "status": "open",
  "expected_close_date": "2026-07-15T00:00:00Z",
  "created_at": "2026-06-12T10:10:00Z",
  "updated_at": "2026-06-12T10:10:00Z"
}

Search deals

POST /crm/deals/search

Faceted, server-paginated deal search. Every filter is optional; an empty body matches every deal in the organization. Filters are sent in the JSON body, while limit and cursor are query params.

Auth: Scope READ_CRM · Org permission view_contacts

ParameterInTypeDescription
limitqueryintPage size, 1 to 200 (default 50).
cursorquerystringOpaque cursor from a previous page's pagination.next_cursor. Omit for the first page.

Request body

FieldTypeRequiredDescription
querystringnoCase-insensitive match on deal name.
statusesstring[]noAny of open, won, lost.
pipeline_idsstring[]noRestrict to any of these pipelines.
stage_idsstring[]noRestrict to any of these stages.
assigned_tostring[]noOwner is any of these user IDs.
campaign_idsstring[]noAttributed campaign is any of these.
min_valuenumbernoValue greater than or equal to.
max_valuenumbernoValue less than or equal to.
close_afterstring (date-time)noExpected close date on or after.
close_beforestring (date-time)noExpected close date on or before.
created_afterstring (date-time)noCreated on or after.
created_beforestring (date-time)noCreated on or before.
sort_bystringnoOne of created_at, updated_at, value, expected_close_date, name.
reversebooleannotrue sorts ascending, false (default) descending.
{
  "query": "renewal",
  "statuses": ["open"],
  "pipeline_ids": ["5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f"],
  "min_value": 1000,
  "sort_by": "value",
  "reverse": false
}

Response

A data array of deal objects (with joined contact, stage, and campaign_name) plus the standard pagination envelope (opaque next_cursor) with an exact total.

{
  "data": [
    {
      "id": "1f2e3d4c-5b6a-4789-90ab-cdef01234567",
      "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
      "pipeline_id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
      "stage_id": "7f3c1a90-2b4d-4e6f-8a01-b2c3d4e5f607",
      "name": "Acme renewal",
      "value": 12000,
      "currency": "USD",
      "status": "open",
      "created_at": "2026-06-01T12:00:00Z",
      "updated_at": "2026-06-10T09:30:00Z"
    }
  ],
  "pagination": {
    "total": 137,
    "next_cursor": "o1_NTA",
    "has_more": true
  }
}

Deals summary

POST /crm/deals/summary

Aggregate counts and value sums over the same filter body as deal search, so header totals and per-stage board column totals reflect the whole matching set rather than a single page.

Auth: Scope READ_CRM · Org permission view_contacts

Request body

Identical to search deals. All facets are optional; an empty body summarizes every deal in the organization.

{
  "pipeline_ids": ["5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f"],
  "statuses": ["open", "won"]
}

Response

Returns the aggregate object. stages holds a per-stage count and open-deal value. mixed_currency is true when matching deals span more than one currency, in which case the top-level value sums should be treated as approximate.

{
  "total": 42,
  "open_count": 30,
  "open_value": 415000,
  "won_count": 9,
  "won_value": 220000,
  "lost_count": 3,
  "lost_value": 0,
  "currency": "USD",
  "stages": [
    {
      "stage_id": "7f3c1a90-2b4d-4e6f-8a01-b2c3d4e5f607",
      "count": 18,
      "value": 240000
    }
  ],
  "mixed_currency": false
}

Get deal

GET /crm/deals/:id

Fetch a single deal.

Auth: Scope READ_CRM · Org permission view_contacts

ParameterInTypeDescription
idpathuuidDeal ID.

Response

Returns the deal object. Joined contact, stage, and campaign_name are populated by the list and search queries, not by this single-row read.

{
  "id": "1f2e3d4c-5b6a-4789-90ab-cdef01234567",
  "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
  "pipeline_id": "5d8a2b1e-0c4f-4a9b-9f2e-1a2b3c4d5e6f",
  "stage_id": "7f3c1a90-2b4d-4e6f-8a01-b2c3d4e5f607",
  "contact_id": "aa11bb22-cc33-4dd4-95ee-66ff77008811",
  "name": "Acme renewal",
  "value": 12000,
  "currency": "USD",
  "status": "open",
  "expected_close_date": "2026-07-15T00:00:00Z",
  "created_at": "2026-06-01T12:00:00Z",
  "updated_at": "2026-06-10T09:30:00Z"
}

Update deal

PATCH /crm/deals/:id

Update a deal. Moving it to a different stage_id records a stage-change activity, and setting status to won or lost stamps the corresponding close timestamp.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidDeal ID.

Request body

FieldTypeRequiredDescription
stage_iduuidnoMove the deal to this stage.
contact_iduuidnoLinked contact.
namestringnoDeal name.
valuenumbernoMonetary value.
currencystringnoISO currency code.
statusstringnoOne of open, won, lost.
expected_close_datestring (date-time)noExpected close date.
lost_reasonstringnoReason recorded when marking lost.
assigned_touuidnoOwner (org member user ID).
{
  "stage_id": "9c0d1e2f-3a4b-4c5d-6e7f-8a9b0c1d2e3f",
  "status": "won"
}

Response

Returns the updated deal object.

Delete deal

DELETE /crm/deals/:id

Delete a deal.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidDeal ID.

Response

204 No Content.

List task types

GET /crm/task-types

List the organization's CRM task types (the kinds of work a task represents, such as Call, Email, or Meeting). A default set is seeded the first time an org lists its types.

Auth: Scope READ_CRM · Org permission view_contacts

Response

Returns a data array of task type objects (no pagination envelope).

{
  "data": [
    {
      "id": "e1d2c3b4-a5f6-4071-8293-a4b5c6d7e8f9",
      "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
      "name": "Call",
      "color": "#8b5cf6",
      "position": 0,
      "created_at": "2026-05-01T09:00:00Z",
      "updated_at": "2026-05-01T09:00:00Z"
    }
  ]
}

Create task type

POST /crm/task-types

Create a CRM task type.

Auth: Scope WRITE_CRM · Org permission manage_contacts

Request body

FieldTypeRequiredDescription
namestringyesType name (1 to 60 characters).
colorstringnoType color (hex).
{
  "name": "Demo",
  "color": "#22c55e"
}

Response

201 Created with the created task type object.

{
  "id": "f0e1d2c3-b4a5-4607-8293-a4b5c6d7e8f9",
  "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
  "name": "Demo",
  "color": "#22c55e",
  "position": 3,
  "created_at": "2026-06-12T10:20:00Z",
  "updated_at": "2026-06-12T10:20:00Z"
}

Update task type

PATCH /crm/task-types/:id

Rename, recolor, or reorder a task type.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidTask type ID.

Request body

FieldTypeRequiredDescription
namestringnoNew type name.
colorstringnoNew type color (hex).
positionintnoNew ordering position.
{
  "name": "Product demo",
  "position": 1
}

Response

Returns the updated task type object.

Delete task type

DELETE /crm/task-types/:id

Delete a task type. Tasks reference their type by name, so existing tasks keep their label and fall back to a neutral color rather than being orphaned.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidTask type ID.

Response

204 No Content.

List tasks

GET /crm/tasks

List CRM tasks with optional contact, deal, assignee, and status filters, keyset-paginated.

Auth: Scope READ_CRM · Org permission view_contacts

ParameterInTypeDescription
contact_idqueryuuidRestrict to tasks linked to this contact.
deal_idqueryuuidRestrict to tasks linked to this deal.
assigned_toqueryuuidRestrict to tasks assigned to this user.
statusquerystringOne of pending, in_progress, completed, cancelled.
cursorquerystringOpaque cursor from a previous page's pagination.next_cursor.
limitqueryintPage size, 1 to 100 (default 50).

Response

A data array of task objects plus a keyset pagination envelope.

{
  "data": [
    {
      "id": "3b4c5d6e-7f80-4912-a3b4-c5d6e7f80912",
      "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
      "contact_id": "aa11bb22-cc33-4dd4-95ee-66ff77008811",
      "deal_id": "1f2e3d4c-5b6a-4789-90ab-cdef01234567",
      "assigned_to": "bb22cc33-dd44-4ee5-86ff-770011223344",
      "created_by": "bb22cc33-dd44-4ee5-86ff-770011223344",
      "title": "Send renewal quote",
      "description": "Include the multi-year discount",
      "due_date": "2026-06-20T17:00:00Z",
      "priority": "high",
      "type": "Email",
      "status": "pending",
      "created_at": "2026-06-12T08:00:00Z",
      "updated_at": "2026-06-12T08:00:00Z"
    }
  ],
  "pagination": {
    "total": null,
    "next_cursor": "c1_b3BhcXVlLWN1cnNvcg",
    "has_more": false
  }
}

Create task

POST /crm/tasks

Create a CRM task, optionally linked to a contact and deal and assigned to a user or team.

Auth: Scope WRITE_CRM · Org permission manage_contacts

Request body

FieldTypeRequiredDescription
titlestringyesTask title (1 to 255 characters).
contact_iduuidnoLinked contact.
deal_iduuidnoLinked deal.
assigned_touuidnoAssignee user ID.
assigned_team_iduuidnoAssignee team ID.
descriptionstringnoFree-text description.
due_datestring (date-time)noDue date.
prioritystringnoOne of low, medium, high, urgent.
typestringnoTask type name (matches a configured task type).
{
  "title": "Send renewal quote",
  "contact_id": "aa11bb22-cc33-4dd4-95ee-66ff77008811",
  "deal_id": "1f2e3d4c-5b6a-4789-90ab-cdef01234567",
  "assigned_to": "bb22cc33-dd44-4ee5-86ff-770011223344",
  "due_date": "2026-06-20T17:00:00Z",
  "priority": "high",
  "type": "Email"
}

Response

201 Created with the created task object. created_by is set to the authenticated user.

{
  "id": "3b4c5d6e-7f80-4912-a3b4-c5d6e7f80912",
  "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
  "contact_id": "aa11bb22-cc33-4dd4-95ee-66ff77008811",
  "deal_id": "1f2e3d4c-5b6a-4789-90ab-cdef01234567",
  "assigned_to": "bb22cc33-dd44-4ee5-86ff-770011223344",
  "created_by": "bb22cc33-dd44-4ee5-86ff-770011223344",
  "title": "Send renewal quote",
  "due_date": "2026-06-20T17:00:00Z",
  "priority": "high",
  "type": "Email",
  "status": "pending",
  "created_at": "2026-06-12T10:25:00Z",
  "updated_at": "2026-06-12T10:25:00Z"
}

Search tasks

POST /crm/tasks/search

Faceted, server-paginated task search. Every filter is optional; an empty body matches every task in the organization. Filters are sent in the JSON body, while limit and cursor are query params.

Auth: Scope READ_CRM · Org permission view_contacts

ParameterInTypeDescription
limitqueryintPage size, 1 to 200 (default 50).
cursorquerystringOpaque cursor from a previous page's pagination.next_cursor. Omit for the first page.

Request body

FieldTypeRequiredDescription
querystringnoCase-insensitive match on task title.
statusesstring[]noAny of pending, in_progress, completed, cancelled.
prioritiesstring[]noAny of low, medium, high, urgent.
typesstring[]noTask type name is any of these.
assigned_tostring[]noAssignee user ID is any of these.
team_idsuuid[]noTask team is any of these, or the assignee belongs to one.
contact_idstringnoLinked contact.
deal_idstringnoLinked deal.
due_afterstring (date-time)noDue on or after.
due_beforestring (date-time)noDue on or before.
overduebooleannoOnly tasks past due and not completed or cancelled.
sort_bystringnoOne of created_at, due_date, priority, title, updated_at.
reversebooleannotrue sorts ascending, false (default) descending.
{
  "statuses": ["pending", "in_progress"],
  "priorities": ["high", "urgent"],
  "overdue": true,
  "sort_by": "due_date",
  "reverse": true
}

Response

A data array of task objects plus the standard pagination envelope (opaque next_cursor) with an exact total.

{
  "data": [
    {
      "id": "3b4c5d6e-7f80-4912-a3b4-c5d6e7f80912",
      "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
      "created_by": "bb22cc33-dd44-4ee5-86ff-770011223344",
      "title": "Send renewal quote",
      "priority": "high",
      "type": "Email",
      "status": "pending",
      "due_date": "2026-06-20T17:00:00Z",
      "created_at": "2026-06-12T08:00:00Z",
      "updated_at": "2026-06-12T08:00:00Z"
    }
  ],
  "pagination": {
    "total": 64,
    "next_cursor": "o1_NTA",
    "has_more": true
  }
}

Tasks summary

POST /crm/tasks/summary

Aggregate counts over the same filter body as task search, so header totals (by status, overdue, high priority) reflect the whole matching set rather than a single page.

Auth: Scope READ_CRM · Org permission view_contacts

Request body

Identical to search tasks. All facets are optional; an empty body summarizes every task in the organization.

{
  "assigned_to": ["bb22cc33-dd44-4ee5-86ff-770011223344"]
}

Response

Returns the aggregate counts.

{
  "total": 64,
  "pending_count": 28,
  "in_progress_count": 9,
  "completed_count": 22,
  "cancelled_count": 5,
  "overdue_count": 7,
  "high_priority_count": 11
}

Get task

GET /crm/tasks/:id

Fetch a single CRM task.

Auth: Scope READ_CRM · Org permission view_contacts

ParameterInTypeDescription
idpathuuidTask ID.

Response

Returns the task object (same shape as a list item).

{
  "id": "3b4c5d6e-7f80-4912-a3b4-c5d6e7f80912",
  "organization_id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
  "created_by": "bb22cc33-dd44-4ee5-86ff-770011223344",
  "title": "Send renewal quote",
  "priority": "high",
  "type": "Email",
  "status": "pending",
  "due_date": "2026-06-20T17:00:00Z",
  "created_at": "2026-06-12T08:00:00Z",
  "updated_at": "2026-06-12T08:00:00Z"
}

Update task

PATCH /crm/tasks/:id

Update a CRM task. Setting status to completed stamps the completion timestamp, and any other status clears it.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidTask ID.

Request body

FieldTypeRequiredDescription
titlestringnoTask title.
assigned_touuidnoAssignee user ID.
assigned_team_iduuidnoAssignee team ID.
descriptionstringnoFree-text description.
due_datestring (date-time)noDue date.
prioritystringnoOne of low, medium, high, urgent.
typestringnoTask type name.
statusstringnoOne of pending, in_progress, completed, cancelled.
{
  "status": "completed"
}

Response

Returns the updated task object.

Delete task

DELETE /crm/tasks/:id

Delete a CRM task.

Auth: Scope WRITE_CRM · Org permission manage_contacts

ParameterInTypeDescription
idpathuuidTask ID.

Response

204 No Content.

Selecting many tasks

The two bulk endpoints below take the same selection, in one of two forms. Either an explicit list of ids:

{ "tasks": ["3b4c5d6e-7f80-4912-a3b4-c5d6e7f80912"] }

Or every task a search matches, minus the ones taken back out:

{
  "all": true,
  "filters": { "statuses": ["pending"], "query": "out_of_office" },
  "exclude": ["3b4c5d6e-7f80-4912-a3b4-c5d6e7f80912"]
}
FieldTypeRequiredDescription
tasksstring[]with all unsetTask ids. At most 1000 per request.
allbooleannoSwitches the selection from tasks to filters.
filtersobjectwith allThe same body search tasks takes, so the set acted on is exactly the set the search returns.
excludestring[]noIds to drop from the resolved set. Ignored unless all is set.

A selection resolving to more than 50,000 tasks is refused with selection_too_large rather than half applied; narrow the filter and run it in parts. A filters block naming something that is not an id in assigned_to, contact_id or deal_id is refused with invalid_filter.

A bulk update raises one crm.task_updated webhook for the whole call rather than one per task, carrying metadata.bulk and metadata.count and no entity_id. A bulk delete raises none, because deleting a task raises none either way.

Neither endpoint takes an Idempotency-Key: both write an end state rather than a delta, so repeating one converges on the same result. They differ in what the repeat reports. A repeated delete finds fewer rows and affected falls to 0. A repeated update still touches every row in the selection, because it stamps updated_at, so affected does not fall; the status and priority simply do not change, and a repeated completion adds no second entry to the contact timeline and does not move completed_at.

Bulk update tasks

PATCH /crm/tasks

Write a status, a priority, or both onto every task in a selection. At least one of the two fields is required; setting status to completed stamps the completion timestamp and records the completion on each linked contact's timeline, exactly as the single-task update does. Any other status clears completed_at, so a task moved back to pending does not keep the time it was finished, again matching the single-task update.

Auth: Scope WRITE_CRM · Org permission manage_contacts

Request body

The selection above, plus:

FieldTypeRequiredDescription
statusstringnoOne of pending, in_progress, completed, cancelled.
prioritystringnoOne of low, medium, high, urgent.
{
  "all": true,
  "filters": { "statuses": ["pending"], "priorities": ["high"] },
  "status": "completed"
}

Response

The number of tasks written, rather than the rows: a select-all can cover tens of thousands.

{ "affected": 128 }

Bulk delete tasks

DELETE /crm/tasks

Delete every task in a selection. The body is the selection object, or a bare array of ids.

Auth: Scope WRITE_CRM · Org permission manage_contacts

{ "all": true, "filters": { "query": "out_of_office" } }

Response

{ "affected": 128 }

Errors

All endpoints return the standard error envelope on failure, for example a malformed UUID path param or an invalid request body. See error codes for the full list.

{
  "error": "invalid_request",
  "message": "invalid request body",
  "code": "INVALID_REQUEST",
  "request_id": "req_8f2c1a90b34d4e6f"
}

On this page