Purple CRM API Documentation

Machine-readable OpenAPI 3.0 specification: https://marketplace.moveworks.com/api/purple-suite/crm/openapi.json

The consolidated all-apps document at https://marketplace.moveworks.com/api/purple-suite/openapi.json can be narrowed to this app with ?apps=crm, or combined with other apps comma-separated (e.g. ?apps=crm,itsm).

Overview

--- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

Endpoints

GET /api/purple-suite/crm/accounts

List CRM accounts (customer companies)

Sales-relationship records for companies this org sells to — customers, prospects and partners — with owner, territory, industry, ARR/MRR and renewal dates. These are not bank deposit accounts (see the retail-banking app) and not user/login accounts (see the identity app). Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Account

    • id string
    • name string
    • industry string
    • territory string
    • status string
    • owner string
    • employees integer
    • annualRecurringRevenue number
    • monthlyRecurringRevenue number
    • averageDealSize number
    • totalOpportunities integer
    • openOpportunities integer
    • closedWonOpportunities integer
    • closedLostOpportunities integer
    • churnRate number
    • netPromoterScore integer
    • supportTickets integer
    • officeCount integer
    • marketSharePct number
    • growthRate number
    • fundingAmount number
    • lastContactDate string
    • nextRenewalDate string
    • lastModifiedAt string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/crm/accounts

Create CRM account (customer company)

Adds a new company record to the CRM. Use this for an organisation you sell to; an individual person at a known company belongs in contacts, and an unqualified inbound person belongs in leads.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Account

  • id string
  • name string
  • industry string
  • territory string
  • status string
  • owner string
  • employees integer
  • annualRecurringRevenue number
  • monthlyRecurringRevenue number
  • averageDealSize number
  • totalOpportunities integer
  • openOpportunities integer
  • closedWonOpportunities integer
  • closedLostOpportunities integer
  • churnRate number
  • netPromoterScore integer
  • supportTickets integer
  • officeCount integer
  • marketSharePct number
  • growthRate number
  • fundingAmount number
  • lastContactDate string
  • nextRenewalDate string
  • lastModifiedAt string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Account

  • id string
  • name string
  • industry string
  • territory string
  • status string
  • owner string
  • employees integer
  • annualRecurringRevenue number
  • monthlyRecurringRevenue number
  • averageDealSize number
  • totalOpportunities integer
  • openOpportunities integer
  • closedWonOpportunities integer
  • closedLostOpportunities integer
  • churnRate number
  • netPromoterScore integer
  • supportTickets integer
  • officeCount integer
  • marketSharePct number
  • growthRate number
  • fundingAmount number
  • lastContactDate string
  • nextRenewalDate string
  • lastModifiedAt string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/crm/accounts/{id}

Get CRM account by id

Fetches one customer company (e.g. ACC-0001) with its firmographics and revenue rollups. Use this when you already have the account id; to find an account by name or attribute, use the list tool with _search or _filter.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Account

  • id string
  • name string
  • industry string
  • territory string
  • status string
  • owner string
  • employees integer
  • annualRecurringRevenue number
  • monthlyRecurringRevenue number
  • averageDealSize number
  • totalOpportunities integer
  • openOpportunities integer
  • closedWonOpportunities integer
  • closedLostOpportunities integer
  • churnRate number
  • netPromoterScore integer
  • supportTickets integer
  • officeCount integer
  • marketSharePct number
  • growthRate number
  • fundingAmount number
  • lastContactDate string
  • nextRenewalDate string
  • lastModifiedAt string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/crm/accounts/{id}

Delete CRM account (destructive)

Permanently removes the company record; this cannot be undone and related contacts, opportunities and cases are not cascaded or cleaned up. Prefer updating status to 'inactive' unless the record genuinely must be purged.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/crm/accounts/{id}

Update CRM account

Partial update of a customer company — e.g. reassign owner, change status (customer/prospect/partner/inactive), territory, or revenue fields. Send only the fields you want to change.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Account

  • id string
  • name string
  • industry string
  • territory string
  • status string
  • owner string
  • employees integer
  • annualRecurringRevenue number
  • monthlyRecurringRevenue number
  • averageDealSize number
  • totalOpportunities integer
  • openOpportunities integer
  • closedWonOpportunities integer
  • closedLostOpportunities integer
  • churnRate number
  • netPromoterScore integer
  • supportTickets integer
  • officeCount integer
  • marketSharePct number
  • growthRate number
  • fundingAmount number
  • lastContactDate string
  • nextRenewalDate string
  • lastModifiedAt string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Account

  • id string
  • name string
  • industry string
  • territory string
  • status string
  • owner string
  • employees integer
  • annualRecurringRevenue number
  • monthlyRecurringRevenue number
  • averageDealSize number
  • totalOpportunities integer
  • openOpportunities integer
  • closedWonOpportunities integer
  • closedLostOpportunities integer
  • churnRate number
  • netPromoterScore integer
  • supportTickets integer
  • officeCount integer
  • marketSharePct number
  • growthRate number
  • fundingAmount number
  • lastContactDate string
  • nextRenewalDate string
  • lastModifiedAt string
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/crm/contacts

List CRM contacts (named people at customer accounts)

External customer-side individuals — buyers, champions, admins — each linked to an account via accountId, with title, email, phone and active/inactive status. Not employees (see the hris and identity apps) and not unqualified prospects (see leads). Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Contact

    • id string
    • firstName string
    • lastName string
    • email string
    • title string
    • accountId string
    • phone string
    • status string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/crm/contacts

Create CRM contact (person at a customer account)

Adds a person who already belongs to a known account. For an unqualified inbound prospect create a lead instead, and to promote an existing lead use the lead convert tool rather than creating a contact by hand.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Contact

  • id string
  • firstName string
  • lastName string
  • email string
  • title string
  • accountId string
  • phone string
  • status string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Contact

  • id string
  • firstName string
  • lastName string
  • email string
  • title string
  • accountId string
  • phone string
  • status string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/crm/contacts/{id}

Get CRM contact by id

Fetches one customer-side person record by id, including their accountId, title and contact details.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Contact

  • id string
  • firstName string
  • lastName string
  • email string
  • title string
  • accountId string
  • phone string
  • status string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/crm/contacts/{id}

Delete CRM contact (destructive)

Permanently removes the person record; this cannot be undone. Prefer setting status to 'inactive' so engagement history stays attributable.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/crm/contacts/{id}

Update CRM contact

Partial update of a customer-side person — e.g. new title, email, phone, owning account, or flipping status to inactive.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Contact

  • id string
  • firstName string
  • lastName string
  • email string
  • title string
  • accountId string
  • phone string
  • status string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Contact

  • id string
  • firstName string
  • lastName string
  • email string
  • title string
  • accountId string
  • phone string
  • status string
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/crm/opportunities

List CRM opportunities (sales deals in the pipeline)

Revenue deals tied to an account, with stage, amount, probability, closeDate, owner and a server-maintained stageHistory. This is the sales pipeline — customer support tickets are cases and logged touchpoints are activities. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key). Example: _filter="stage ne 'closed_lost' and amount gt 50000".

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Opportunity

    • id string
    • name string
    • accountId string
    • stage string
    • amount number
    • probability integer
    • closeDate string
    • owner string
    • stageHistory arrayPopulated automatically by /advance-stage
      Populated automatically by /advance-stage

      array of:

      • from string
      • to string
      • notes string
      • changedAt string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/crm/opportunities

Create CRM opportunity (new sales deal)

Opens a new pipeline deal against an account with a stage, amount and close date. To move an existing deal forward use the advance-stage tool rather than creating a second opportunity.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Opportunity

  • id string
  • name string
  • accountId string
  • stage string
  • amount number
  • probability integer
  • closeDate string
  • owner string
  • stageHistory arrayPopulated automatically by /advance-stage
    Populated automatically by /advance-stage

    array of:

    • from string
    • to string
    • notes string
    • changedAt string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Opportunity

  • id string
  • name string
  • accountId string
  • stage string
  • amount number
  • probability integer
  • closeDate string
  • owner string
  • stageHistory arrayPopulated automatically by /advance-stage
    Populated automatically by /advance-stage

    array of:

    • from string
    • to string
    • notes string
    • changedAt string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/crm/opportunities/{id}

Get CRM opportunity by id

Fetches one pipeline deal including its stageHistory (the audit trail of stage moves recorded by the advance-stage tool).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Opportunity

  • id string
  • name string
  • accountId string
  • stage string
  • amount number
  • probability integer
  • closeDate string
  • owner string
  • stageHistory arrayPopulated automatically by /advance-stage
    Populated automatically by /advance-stage

    array of:

    • from string
    • to string
    • notes string
    • changedAt string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/crm/opportunities/{id}

Delete CRM opportunity (destructive)

Permanently removes the deal and its stage history; this cannot be undone. To record a deal that did not land, advance its stage to closed_lost instead.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/crm/opportunities/{id}

Update CRM opportunity (non-stage fields)

Partial update of deal attributes — amount, probability, closeDate, owner, name. To change the pipeline stage prefer the advance-stage tool, which enforces the stage order and appends to stageHistory; this tool does neither.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Opportunity

  • id string
  • name string
  • accountId string
  • stage string
  • amount number
  • probability integer
  • closeDate string
  • owner string
  • stageHistory arrayPopulated automatically by /advance-stage
    Populated automatically by /advance-stage

    array of:

    • from string
    • to string
    • notes string
    • changedAt string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Opportunity

  • id string
  • name string
  • accountId string
  • stage string
  • amount number
  • probability integer
  • closeDate string
  • owner string
  • stageHistory arrayPopulated automatically by /advance-stage
    Populated automatically by /advance-stage

    array of:

    • from string
    • to string
    • notes string
    • changedAt string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/crm/opportunities/{id}/advance-stage

Advance a CRM opportunity to a new pipeline stage

The preferred way to move a deal: sets the stage, appends a timestamped stageHistory entry with optional notes, and enforces pipeline order. Forward-only and terminal — moving backwards is rejected, and a closed_won/closed_lost deal cannot be advanced at all. Use PATCH /opportunities/{id} for non-stage edits.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json — required

  • stage string required
  • notes string

Responses

200
Updated opportunity with new stage and stageHistory entry

application/json

Opportunity

  • id string
  • name string
  • accountId string
  • stage string
  • amount number
  • probability integer
  • closeDate string
  • owner string
  • stageHistory arrayPopulated automatically by /advance-stage
    Populated automatically by /advance-stage

    array of:

    • from string
    • to string
    • notes string
    • changedAt string
  • createdAt string
  • updatedAt string
400
Invalid stage value
404
Not found
422
Stage transition not allowed (opportunity is closed or move is backwards)

GET /api/purple-suite/crm/leads

List CRM leads (unqualified inbound prospects)

Top-of-funnel people not yet tied to an account, with source (web form, trade show, referral…), score and status new → contacted → qualified → converted/disqualified/dead. Once qualified, a lead becomes a contact (and optionally an opportunity) via the convert tool. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Lead

    • id string
    • firstName string
    • lastName string
    • company string
    • email string
    • phone stringOptional phone number. If present, copied to the Contact created by /convert.
    • title stringOptional job title. If present, copied to the Contact created by /convert.
    • accountId stringOptional linked account ID. If present, copied to the Contact created by /convert.
    • status string
    • source string
    • score integer
    • convertedTo objectSet automatically by /convert
      Set automatically by /convert
      • contactId string
      • opportunityId string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/crm/leads

Create CRM lead (raw inbound prospect)

Captures a new unqualified prospect with their company, source and score. Use contacts instead when the person already belongs to a known customer account.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Lead

  • id string
  • firstName string
  • lastName string
  • company string
  • email string
  • phone stringOptional phone number. If present, copied to the Contact created by /convert.
  • title stringOptional job title. If present, copied to the Contact created by /convert.
  • accountId stringOptional linked account ID. If present, copied to the Contact created by /convert.
  • status string
  • source string
  • score integer
  • convertedTo objectSet automatically by /convert
    Set automatically by /convert
    • contactId string
    • opportunityId string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Lead

  • id string
  • firstName string
  • lastName string
  • company string
  • email string
  • phone stringOptional phone number. If present, copied to the Contact created by /convert.
  • title stringOptional job title. If present, copied to the Contact created by /convert.
  • accountId stringOptional linked account ID. If present, copied to the Contact created by /convert.
  • status string
  • source string
  • score integer
  • convertedTo objectSet automatically by /convert
    Set automatically by /convert
    • contactId string
    • opportunityId string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/crm/leads/{id}

Get CRM lead by id

Fetches one prospect record, including convertedTo once the lead has been run through the convert tool.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Lead

  • id string
  • firstName string
  • lastName string
  • company string
  • email string
  • phone stringOptional phone number. If present, copied to the Contact created by /convert.
  • title stringOptional job title. If present, copied to the Contact created by /convert.
  • accountId stringOptional linked account ID. If present, copied to the Contact created by /convert.
  • status string
  • source string
  • score integer
  • convertedTo objectSet automatically by /convert
    Set automatically by /convert
    • contactId string
    • opportunityId string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/crm/leads/{id}

Delete CRM lead (destructive)

Permanently removes the prospect record; this cannot be undone. Prefer status 'disqualified' or 'dead' so funnel conversion rates stay accurate.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/crm/leads/{id}

Update CRM lead

Partial update — e.g. move status from new to contacted or qualified, adjust score, or fill in phone/title/accountId. Do not set status to 'converted' here; use the convert tool so the contact (and optional opportunity) are actually created.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Lead

  • id string
  • firstName string
  • lastName string
  • company string
  • email string
  • phone stringOptional phone number. If present, copied to the Contact created by /convert.
  • title stringOptional job title. If present, copied to the Contact created by /convert.
  • accountId stringOptional linked account ID. If present, copied to the Contact created by /convert.
  • status string
  • source string
  • score integer
  • convertedTo objectSet automatically by /convert
    Set automatically by /convert
    • contactId string
    • opportunityId string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Lead

  • id string
  • firstName string
  • lastName string
  • company string
  • email string
  • phone stringOptional phone number. If present, copied to the Contact created by /convert.
  • title stringOptional job title. If present, copied to the Contact created by /convert.
  • accountId stringOptional linked account ID. If present, copied to the Contact created by /convert.
  • status string
  • source string
  • score integer
  • convertedTo objectSet automatically by /convert
    Set automatically by /convert
    • contactId string
    • opportunityId string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/crm/leads/{id}/convert

Convert a CRM lead into a contact (and optionally an opportunity)

The one-way qualification step: creates a Contact from the lead's fields, additionally creates an Opportunity when opportunity_data is supplied, and stamps the lead as 'converted'. Not repeatable and not reversible — a lead that is already converted or disqualified is rejected.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json

  • opportunity_data objectOptional. If provided, an Opportunity is created by spreading these fields.
    Optional. If provided, an Opportunity is created by spreading these fields.
    • name string
    • accountId string
    • stage string
    • amount number
    • probability integer
    • closeDate string
    • owner string

Responses

200
Converted lead with the created contact and optional opportunity

application/json

  • contact object

    Contact

    • id string
    • firstName string
    • lastName string
    • email string
    • title string
    • accountId string
    • phone string
    • status string
    • createdAt string
    • updatedAt string
  • opportunity object

    Opportunity

    • id string
    • name string
    • accountId string
    • stage string
    • amount number
    • probability integer
    • closeDate string
    • owner string
    • stageHistory arrayPopulated automatically by /advance-stage
      Populated automatically by /advance-stage

      array of:

      • from string
      • to string
      • notes string
      • changedAt string
    • createdAt string
    • updatedAt string
  • lead object

    Lead

    • id string
    • firstName string
    • lastName string
    • company string
    • email string
    • phone stringOptional phone number. If present, copied to the Contact created by /convert.
    • title stringOptional job title. If present, copied to the Contact created by /convert.
    • accountId stringOptional linked account ID. If present, copied to the Contact created by /convert.
    • status string
    • source string
    • score integer
    • convertedTo objectSet automatically by /convert
      Set automatically by /convert
      • contactId string
      • opportunityId string
    • createdAt string
    • updatedAt string
404
Not found
422
Lead cannot be converted (already converted or disqualified)

GET /api/purple-suite/crm/cases

List CRM cases (customer support tickets)

Issues reported by a customer against an account, with subject, priority, assignee and status open → in_progress → resolved → closed. These are customer-facing support cases; internal employee IT tickets live in the itsm app. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Case

    • id string
    • subject string
    • accountId string
    • status string
    • priority string
    • assignee string
    • resolvedAt string
    • reopenReason stringRequired when reopening a closed case (stored field name). See also: reopen_reason.
    • reopen_reason stringInput alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/crm/cases

Create CRM case (customer support ticket)

Logs a new customer-reported issue against an account with a subject and priority. For an internal employee IT problem create an itsm incident instead.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Case

  • id string
  • subject string
  • accountId string
  • status string
  • priority string
  • assignee string
  • resolvedAt string
  • reopenReason stringRequired when reopening a closed case (stored field name). See also: reopen_reason.
  • reopen_reason stringInput alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Case

  • id string
  • subject string
  • accountId string
  • status string
  • priority string
  • assignee string
  • resolvedAt string
  • reopenReason stringRequired when reopening a closed case (stored field name). See also: reopen_reason.
  • reopen_reason stringInput alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.
  • createdAt string
  • updatedAt string

GET /api/purple-suite/crm/cases/{id}

Get CRM case by id

Fetches one customer support case with its status, priority, assignee and resolution timestamps.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Case

  • id string
  • subject string
  • accountId string
  • status string
  • priority string
  • assignee string
  • resolvedAt string
  • reopenReason stringRequired when reopening a closed case (stored field name). See also: reopen_reason.
  • reopen_reason stringInput alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/crm/cases/{id}

Delete CRM case (destructive)

Permanently removes the support case and its history; this cannot be undone. Resolving or closing the case is almost always the right action instead.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/crm/cases/{id}

Update or transition a CRM case

Partial update, and the only way to move a case through its lifecycle. State-machine rules are enforced: an 'open' case cannot jump straight to 'closed' (route it via in_progress or resolved), and reopening a 'closed' case requires reopenReason (alias reopen_reason).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Case

  • id string
  • subject string
  • accountId string
  • status string
  • priority string
  • assignee string
  • resolvedAt string
  • reopenReason stringRequired when reopening a closed case (stored field name). See also: reopen_reason.
  • reopen_reason stringInput alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Case

  • id string
  • subject string
  • accountId string
  • status string
  • priority string
  • assignee string
  • resolvedAt string
  • reopenReason stringRequired when reopening a closed case (stored field name). See also: reopen_reason.
  • reopen_reason stringInput alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.
  • createdAt string
  • updatedAt string
404
Not found
422
State-machine violation — e.g. closing an open case directly, or reopening without reopenReason

GET /api/purple-suite/crm/activities

List CRM activities (logged calls, emails, meetings, tasks, notes)

The touchpoint timeline attached to an account: type, subject, owner, dueDate and a completed flag. Use it for engagement history or to find open follow-ups; real calendar invites live in the calendar app. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01".
$selectquerystringnoComma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Activity

    • id string
    • type string
    • subject string
    • accountId string
    • owner string
    • completed boolean
    • dueDate string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400
Invalid OData query (e.g. malformed $filter)

POST /api/purple-suite/crm/activities

Log a CRM activity (call, email, meeting, task or note)

Records a touchpoint that happened, or schedules a follow-up task, against an account. This only writes a CRM record — it does not send an email or book a meeting.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Activity

  • id string
  • type string
  • subject string
  • accountId string
  • owner string
  • completed boolean
  • dueDate string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Activity

  • id string
  • type string
  • subject string
  • accountId string
  • owner string
  • completed boolean
  • dueDate string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/crm/activities/{id}

Get CRM activity by id

Fetches one logged touchpoint or follow-up task from an account's timeline.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Activity

  • id string
  • type string
  • subject string
  • accountId string
  • owner string
  • completed boolean
  • dueDate string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/crm/activities/{id}

Delete CRM activity (destructive)

Permanently removes the touchpoint from the account timeline; this cannot be undone. Mark a task completed instead of deleting it when the work simply finished.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/crm/activities/{id}

Update CRM activity

Partial update of a logged touchpoint — most often flipping completed to true when a follow-up is done, or changing dueDate, owner or subject.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Activity

  • id string
  • type string
  • subject string
  • accountId string
  • owner string
  • completed boolean
  • dueDate string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Activity

  • id string
  • type string
  • subject string
  • accountId string
  • owner string
  • completed boolean
  • dueDate string
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/crm/recent-campaign

Get most recent campaign (legacy stub)

Legacy compatibility endpoint. Returns a static most-recent campaign record. No authentication required.

Responses

200
Most recent campaign

application/json

  • campaign object
    • id string
    • name string
    • status string

GET /api/purple-suite/crm/engagement

Get customer engagement data (legacy stub)

Legacy compatibility endpoint. Returns static engagement metrics. No authentication required.

Responses

200
Engagement metrics

application/json

  • engagement object
    • totalUsers integer
    • activeUsers integer
    • engagementRate number