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
Endpoints
GET /api/purple-suite/crm/accounts
List CRM accounts (customer companies)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
AccountidstringnamestringindustrystringterritorystringstatusstringownerstringemployeesintegerannualRecurringRevenuenumbermonthlyRecurringRevenuenumberaverageDealSizenumbertotalOpportunitiesintegeropenOpportunitiesintegerclosedWonOpportunitiesintegerclosedLostOpportunitiesintegerchurnRatenumbernetPromoterScoreintegersupportTicketsintegerofficeCountintegermarketSharePctnumbergrowthRatenumberfundingAmountnumberlastContactDatestringnextRenewalDatestringlastModifiedAtstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Account
idstringnamestringindustrystringterritorystringstatusstringownerstringemployeesintegerannualRecurringRevenuenumbermonthlyRecurringRevenuenumberaverageDealSizenumbertotalOpportunitiesintegeropenOpportunitiesintegerclosedWonOpportunitiesintegerclosedLostOpportunitiesintegerchurnRatenumbernetPromoterScoreintegersupportTicketsintegerofficeCountintegermarketSharePctnumbergrowthRatenumberfundingAmountnumberlastContactDatestringnextRenewalDatestringlastModifiedAtstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonAccountidstringnamestringindustrystringterritorystringstatusstringownerstringemployeesintegerannualRecurringRevenuenumbermonthlyRecurringRevenuenumberaverageDealSizenumbertotalOpportunitiesintegeropenOpportunitiesintegerclosedWonOpportunitiesintegerclosedLostOpportunitiesintegerchurnRatenumbernetPromoterScoreintegersupportTicketsintegerofficeCountintegermarketSharePctnumbergrowthRatenumberfundingAmountnumberlastContactDatestringnextRenewalDatestringlastModifiedAtstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/crm/accounts/{id}
Get CRM account by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonAccountidstringnamestringindustrystringterritorystringstatusstringownerstringemployeesintegerannualRecurringRevenuenumbermonthlyRecurringRevenuenumberaverageDealSizenumbertotalOpportunitiesintegeropenOpportunitiesintegerclosedWonOpportunitiesintegerclosedLostOpportunitiesintegerchurnRatenumbernetPromoterScoreintegersupportTicketsintegerofficeCountintegermarketSharePctnumbergrowthRatenumberfundingAmountnumberlastContactDatestringnextRenewalDatestringlastModifiedAtstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/crm/accounts/{id}
Delete CRM account (destructive)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/crm/accounts/{id}
Update CRM account
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Account
idstringnamestringindustrystringterritorystringstatusstringownerstringemployeesintegerannualRecurringRevenuenumbermonthlyRecurringRevenuenumberaverageDealSizenumbertotalOpportunitiesintegeropenOpportunitiesintegerclosedWonOpportunitiesintegerclosedLostOpportunitiesintegerchurnRatenumbernetPromoterScoreintegersupportTicketsintegerofficeCountintegermarketSharePctnumbergrowthRatenumberfundingAmountnumberlastContactDatestringnextRenewalDatestringlastModifiedAtstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonAccountidstringnamestringindustrystringterritorystringstatusstringownerstringemployeesintegerannualRecurringRevenuenumbermonthlyRecurringRevenuenumberaverageDealSizenumbertotalOpportunitiesintegeropenOpportunitiesintegerclosedWonOpportunitiesintegerclosedLostOpportunitiesintegerchurnRatenumbernetPromoterScoreintegersupportTicketsintegerofficeCountintegermarketSharePctnumbergrowthRatenumberfundingAmountnumberlastContactDatestringnextRenewalDatestringlastModifiedAtstringcreatedAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/crm/contacts
List CRM contacts (named people at customer accounts)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
ContactidstringfirstNamestringlastNamestringemailstringtitlestringaccountIdstringphonestringstatusstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Contact
idstringfirstNamestringlastNamestringemailstringtitlestringaccountIdstringphonestringstatusstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonContactidstringfirstNamestringlastNamestringemailstringtitlestringaccountIdstringphonestringstatusstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/crm/contacts/{id}
Get CRM contact by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonContactidstringfirstNamestringlastNamestringemailstringtitlestringaccountIdstringphonestringstatusstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/crm/contacts/{id}
Delete CRM contact (destructive)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/crm/contacts/{id}
Update CRM contact
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Contact
idstringfirstNamestringlastNamestringemailstringtitlestringaccountIdstringphonestringstatusstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonContactidstringfirstNamestringlastNamestringemailstringtitlestringaccountIdstringphonestringstatusstringcreatedAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/crm/opportunities
List CRM opportunities (sales deals in the pipeline)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
OpportunityidstringnamestringaccountIdstringstagestringamountnumberprobabilityintegercloseDatestringownerstringstageHistoryarray — Populated automatically by /advance-stagePopulated automatically by /advance-stagearray of:
fromstringtostringnotesstringchangedAtstring
createdAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Opportunity
idstringnamestringaccountIdstringstagestringamountnumberprobabilityintegercloseDatestringownerstringstageHistoryarray — Populated automatically by /advance-stagePopulated automatically by /advance-stagearray of:
fromstringtostringnotesstringchangedAtstring
createdAtstringupdatedAtstring
Responses
201- Created
application/jsonOpportunityidstringnamestringaccountIdstringstagestringamountnumberprobabilityintegercloseDatestringownerstringstageHistoryarray — Populated automatically by /advance-stagePopulated automatically by /advance-stagearray of:
fromstringtostringnotesstringchangedAtstring
createdAtstringupdatedAtstring
GET /api/purple-suite/crm/opportunities/{id}
Get CRM opportunity by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonOpportunityidstringnamestringaccountIdstringstagestringamountnumberprobabilityintegercloseDatestringownerstringstageHistoryarray — Populated automatically by /advance-stagePopulated automatically by /advance-stagearray of:
fromstringtostringnotesstringchangedAtstring
createdAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/crm/opportunities/{id}
Delete CRM opportunity (destructive)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/crm/opportunities/{id}
Update CRM opportunity (non-stage fields)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Opportunity
idstringnamestringaccountIdstringstagestringamountnumberprobabilityintegercloseDatestringownerstringstageHistoryarray — Populated automatically by /advance-stagePopulated automatically by /advance-stagearray of:
fromstringtostringnotesstringchangedAtstring
createdAtstringupdatedAtstring
Responses
200- Updated
application/jsonOpportunityidstringnamestringaccountIdstringstagestringamountnumberprobabilityintegercloseDatestringownerstringstageHistoryarray — Populated automatically by /advance-stagePopulated automatically by /advance-stagearray of:
fromstringtostringnotesstringchangedAtstring
createdAtstringupdatedAtstring
404- Not found
POST /api/purple-suite/crm/opportunities/{id}/advance-stage
Advance a CRM opportunity to a new pipeline stage
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json — required
stagestring requirednotesstring
Responses
200- Updated opportunity with new stage and stageHistory entry
application/jsonOpportunityidstringnamestringaccountIdstringstagestringamountnumberprobabilityintegercloseDatestringownerstringstageHistoryarray — Populated automatically by /advance-stagePopulated automatically by /advance-stagearray of:
fromstringtostringnotesstringchangedAtstring
createdAtstringupdatedAtstring
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)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
LeadidstringfirstNamestringlastNamestringcompanystringemailstringphonestring — Optional phone number. If present, copied to the Contact created by /convert.titlestring — Optional job title. If present, copied to the Contact created by /convert.accountIdstring — Optional linked account ID. If present, copied to the Contact created by /convert.statusstringsourcestringscoreintegerconvertedToobject — Set automatically by /convertSet automatically by /convertcontactIdstringopportunityIdstring
createdAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Lead
idstringfirstNamestringlastNamestringcompanystringemailstringphonestring — Optional phone number. If present, copied to the Contact created by /convert.titlestring — Optional job title. If present, copied to the Contact created by /convert.accountIdstring — Optional linked account ID. If present, copied to the Contact created by /convert.statusstringsourcestringscoreintegerconvertedToobject — Set automatically by /convertSet automatically by /convertcontactIdstringopportunityIdstring
createdAtstringupdatedAtstring
Responses
201- Created
application/jsonLeadidstringfirstNamestringlastNamestringcompanystringemailstringphonestring — Optional phone number. If present, copied to the Contact created by /convert.titlestring — Optional job title. If present, copied to the Contact created by /convert.accountIdstring — Optional linked account ID. If present, copied to the Contact created by /convert.statusstringsourcestringscoreintegerconvertedToobject — Set automatically by /convertSet automatically by /convertcontactIdstringopportunityIdstring
createdAtstringupdatedAtstring
GET /api/purple-suite/crm/leads/{id}
Get CRM lead by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonLeadidstringfirstNamestringlastNamestringcompanystringemailstringphonestring — Optional phone number. If present, copied to the Contact created by /convert.titlestring — Optional job title. If present, copied to the Contact created by /convert.accountIdstring — Optional linked account ID. If present, copied to the Contact created by /convert.statusstringsourcestringscoreintegerconvertedToobject — Set automatically by /convertSet automatically by /convertcontactIdstringopportunityIdstring
createdAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/crm/leads/{id}
Delete CRM lead (destructive)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/crm/leads/{id}
Update CRM lead
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Lead
idstringfirstNamestringlastNamestringcompanystringemailstringphonestring — Optional phone number. If present, copied to the Contact created by /convert.titlestring — Optional job title. If present, copied to the Contact created by /convert.accountIdstring — Optional linked account ID. If present, copied to the Contact created by /convert.statusstringsourcestringscoreintegerconvertedToobject — Set automatically by /convertSet automatically by /convertcontactIdstringopportunityIdstring
createdAtstringupdatedAtstring
Responses
200- Updated
application/jsonLeadidstringfirstNamestringlastNamestringcompanystringemailstringphonestring — Optional phone number. If present, copied to the Contact created by /convert.titlestring — Optional job title. If present, copied to the Contact created by /convert.accountIdstring — Optional linked account ID. If present, copied to the Contact created by /convert.statusstringsourcestringscoreintegerconvertedToobject — Set automatically by /convertSet automatically by /convertcontactIdstringopportunityIdstring
createdAtstringupdatedAtstring
404- Not found
POST /api/purple-suite/crm/leads/{id}/convert
Convert a CRM lead into a contact (and optionally an opportunity)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
opportunity_dataobject — Optional. If provided, an Opportunity is created by spreading these fields.Optional. If provided, an Opportunity is created by spreading these fields.namestringaccountIdstringstagestringamountnumberprobabilityintegercloseDatestringownerstring
Responses
200- Converted lead with the created contact and optional opportunity
application/jsoncontactobjectContactidstringfirstNamestringlastNamestringemailstringtitlestringaccountIdstringphonestringstatusstringcreatedAtstringupdatedAtstring
opportunityobjectOpportunityidstringnamestringaccountIdstringstagestringamountnumberprobabilityintegercloseDatestringownerstringstageHistoryarray — Populated automatically by /advance-stagePopulated automatically by /advance-stagearray of:
fromstringtostringnotesstringchangedAtstring
createdAtstringupdatedAtstring
leadobjectLeadidstringfirstNamestringlastNamestringcompanystringemailstringphonestring — Optional phone number. If present, copied to the Contact created by /convert.titlestring — Optional job title. If present, copied to the Contact created by /convert.accountIdstring — Optional linked account ID. If present, copied to the Contact created by /convert.statusstringsourcestringscoreintegerconvertedToobject — Set automatically by /convertSet automatically by /convertcontactIdstringopportunityIdstring
createdAtstringupdatedAtstring
404- Not found
422- Lead cannot be converted (already converted or disqualified)
GET /api/purple-suite/crm/cases
List CRM cases (customer support tickets)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
CaseidstringsubjectstringaccountIdstringstatusstringprioritystringassigneestringresolvedAtstringreopenReasonstring — Required when reopening a closed case (stored field name). See also: reopen_reason.reopen_reasonstring — Input alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.createdAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Case
idstringsubjectstringaccountIdstringstatusstringprioritystringassigneestringresolvedAtstringreopenReasonstring — Required when reopening a closed case (stored field name). See also: reopen_reason.reopen_reasonstring — Input alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.createdAtstringupdatedAtstring
Responses
201- Created
application/jsonCaseidstringsubjectstringaccountIdstringstatusstringprioritystringassigneestringresolvedAtstringreopenReasonstring — Required when reopening a closed case (stored field name). See also: reopen_reason.reopen_reasonstring — Input alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.createdAtstringupdatedAtstring
GET /api/purple-suite/crm/cases/{id}
Get CRM case by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonCaseidstringsubjectstringaccountIdstringstatusstringprioritystringassigneestringresolvedAtstringreopenReasonstring — Required when reopening a closed case (stored field name). See also: reopen_reason.reopen_reasonstring — Input alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.createdAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/crm/cases/{id}
Delete CRM case (destructive)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/crm/cases/{id}
Update or transition a CRM case
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Case
idstringsubjectstringaccountIdstringstatusstringprioritystringassigneestringresolvedAtstringreopenReasonstring — Required when reopening a closed case (stored field name). See also: reopen_reason.reopen_reasonstring — Input alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.createdAtstringupdatedAtstring
Responses
200- Updated
application/jsonCaseidstringsubjectstringaccountIdstringstatusstringprioritystringassigneestringresolvedAtstringreopenReasonstring — Required when reopening a closed case (stored field name). See also: reopen_reason.reopen_reasonstring — Input alias for reopenReason accepted by PATCH /cases/:id. Normalised to reopenReason before storage. Use reopenReason in responses.createdAtstringupdatedAtstring
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)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData 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". |
$select | query | string | no | Comma-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. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
ActivityidstringtypestringsubjectstringaccountIdstringownerstringcompletedbooleandueDatestringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present 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)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Activity
idstringtypestringsubjectstringaccountIdstringownerstringcompletedbooleandueDatestringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonActivityidstringtypestringsubjectstringaccountIdstringownerstringcompletedbooleandueDatestringcreatedAtstringupdatedAtstring
GET /api/purple-suite/crm/activities/{id}
Get CRM activity by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonActivityidstringtypestringsubjectstringaccountIdstringownerstringcompletedbooleandueDatestringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/crm/activities/{id}
Delete CRM activity (destructive)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/crm/activities/{id}
Update CRM activity
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Activity
idstringtypestringsubjectstringaccountIdstringownerstringcompletedbooleandueDatestringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonActivityidstringtypestringsubjectstringaccountIdstringownerstringcompletedbooleandueDatestringcreatedAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/crm/recent-campaign
Get most recent campaign (legacy stub)
Responses
200- Most recent campaign
application/jsoncampaignobjectidstringnamestringstatusstring
GET /api/purple-suite/crm/engagement
Get customer engagement data (legacy stub)
Responses
200- Engagement metrics
application/jsonengagementobjecttotalUsersintegeractiveUsersintegerengagementRatenumber