Developers

REST API reference

Read and write your workspace's records from your own tools. Version 1. Machine-readable: OpenAPI 3.1 file.

Authentication

An admin creates API keys in Setup → API & Webhooks. Each key belongs to one workspace and is either read only or read & write, for all objects or only the ones chosen. The key is shown once when it's created; we keep only a fingerprint (SHA-256), so a lost key can't be shown again — revoke it and create a new one. Revoking takes effect straight away. Setup shows when each key was last used.

Authorization: Bearer xpi_…

Changes made through the API show in each record's history as “via API · key name”, and start playbook rules exactly like changes in the app.

Endpoints

GET /api/v1/{object} — List records

  • limit: 1–1000, default 100
  • offset: Records to skip, default 0
  • updated_since: Only records changed after this ISO 8601 date-time
  • <field>: Exact match on any field key, e.g. ?health=At%20Risk or ?client=<id> (case-insensitive)

Success: 200 a page of records. Errors: 401, 403, 404, 422.

curl "https://xpicrm.com/api/v1/clients?limit=2&health=At%20Risk" \
  -H "Authorization: Bearer $XPI_KEY"
{
  "data": [
    {
      "id": "0b7c…",
      "clientNumber": "C-00012",
      "name": "Globex Inc.",
      "health": "At Risk",
      "arr": 48000,
      "renewalDate": "2026-12-15",
      "owner": { "id": "5f1e…", "name": "Alex Morgan" },
      "daysToRenewal": 68,
      "openBalance": 1200,
      "documents": [{ "id": "f19a…", "name": "contract.pdf", "size": 182044 }],
      "createdAt": "2026-03-02T09:14:00Z",
      "updatedAt": "2026-10-07T10:12:00Z"
    }
  ],
  "total": 1, "limit": 2, "offset": 0
}

POST /api/v1/{object} — Create or update records (upsert)

  • match: Comma-separated field keys used to find an existing record to update (default: unique fields, else the record name)

Success: 201 all records created · 200 all records saved, some or all updated · 207 some records failed; the others were saved. Errors: 401, 403, 404, 422.

curl -X POST "https://xpicrm.com/api/v1/clients?match=name" \
  -H "Authorization: Bearer $XPI_KEY" -H "Content-Type: application/json" \
  -d '[{ "name": "Acme Corp", "website": "https://acme.example" },
       { "name": "New Co", "tier": "Gold", "owner": "alex@example.com" }]'
{ "created": 1, "updated": 1, "errors": [], "ids": ["2c4e…", "9a1d…"] }

GET /api/v1/{object}/{id} — Get one record

Success: 200 the record. Errors: 401, 403, 404.

curl "https://xpicrm.com/api/v1/clients/2c4e…" -H "Authorization: Bearer $XPI_KEY"
{ "data": { "id": "2c4e…", "name": "Acme Corp", "…": "…" } }

PATCH /api/v1/{object}/{id} — Update fields of one record

Success: 200 the updated record. Errors: 401, 403, 404, 422.

curl -X PATCH "https://xpicrm.com/api/v1/clients/2c4e…" \
  -H "Authorization: Bearer $XPI_KEY" -H "Content-Type: application/json" \
  -d '{ "health": "At Risk" }'
{ "data": { "id": "2c4e…", "health": "At Risk", "…": "…" } }

DELETE /api/v1/{object}/{id} — Delete one record

Success: 200 deleted. Errors: 401, 403, 404.

curl -X DELETE "https://xpicrm.com/api/v1/clients/2c4e…" -H "Authorization: Bearer $XPI_KEY"
{ "deleted": "2c4e…" }

Filters and paging

Lists are ordered by creation time. Use limit (up to 1,000, default 100) and offset to page; total is the number of matching records. updated_since returns only records changed after that moment, so you can sync changes. Any other parameter is an exact, case-insensitive match on that field key; for links, pass the linked record's id.

Creating and updating (upserts)

POST accepts one record, an array, or { "records": [...] } (up to 1,000). A record with an id updates that record. Otherwise ?match=field1,field2 finds an existing record with the same values (default: the object's unique fields, otherwise its name) and updates it; if none is found, a new record is created. Updates change only the fields you send; everything else stays as it is. A failed record never stops the others: the response is 207 with an errors list (by position in your request).

Payload format

  • Fields use their keys (below). id, createdAt and updatedAt are always included and can't be set.
  • Links come back as { id, name }. If the key can't access the linked object, only { id }. Send a link as its id, its name, or { id }.
  • People come back as { id, name }. Send an id, an email address or a full name.
  • Files come back as [{ id, name, size }] and can't be set through the API.
  • Calculated values (formulas, roll-ups, fields from linked records) are included and are read only. The health score is read only too.

Errors

Errors look like { "error": { "status": 422, "message": "…" } }.

  • 401: Missing, wrong or revoked API key
  • 403: The key has no access to this object, or it is read-only and the request writes
  • 404: Unknown object or record
  • 405: Method not allowed on this path
  • 422: Validation failed

Objects and fields

The default objects are listed here. Your workspace may have more objects and fields; their keys are shown in Setup → Object Manager.

Clients — /api/v1/clients
KeyLabelFormat
clientNumberClient numberAuto number: filled in when empty
nameClient name (required)Text
industryIndustryText
websiteWebsiteWeb address
tierTierOne of: Enterprise, Mid-Market, SMB
healthHealthOne of: Healthy, At Risk, Critical
arrARRNumber (USD)
renewalDateRenewal dateDate, YYYY-MM-DD
notesNotesText
ownerAccount owner (CSM)Person: { id } or an email or full name; returned as { id, name }
daysToRenewalDays to renewalCalculated: read only
documentsDocumentsFiles: read only, returned as [{ id, name, size }]
openBalanceOpen balanceCalculated: read only
healthScoreHealth scoreNumber
Contacts — /api/v1/contacts
KeyLabelFormat
firstNameFirst name (required)Text
lastNameLast nameText
roleTitleText
clientClientLink to a client: id, name or { id }; returned as { id, name }
isPrimaryPrimary contacttrue / false (CSV: yes/no, 1/0)
emailEmailEmail address
phonePhoneText
notesNotesText
clientHealthClient healthCalculated: read only
Billing — /api/v1/billing
KeyLabelFormat
invoiceNumberInvoice numberAuto number: filled in when empty
clientClient (required)Link to a client: id, name or { id }; returned as { id, name }
billingContactBilling contactLink to a contact: id, name or { id }; returned as { id, name }
statusStatus (required)One of: Draft, Pending to send, Sent, Void
paymentStatusPayment statusCalculated: read only
poNumberPO numberText
issueDateIssue dateDate, YYYY-MM-DD
paymentTermsPayment termsOne of: Due on receipt, Net 15, Net 30, Net 45, Net 60
dueDateDue dateCalculated: read only
daysOverdueDays overdueCalculated: read only
sentDateSent onDate, YYYY-MM-DD
periodStartService period startDate, YYYY-MM-DD
periodEndService period endDate, YYYY-MM-DD
subtotalSubtotalCalculated: read only
discountDiscountNumber (USD)
taxRateTax rateNumber, e.g. 12.5 for 12.5%
taxAmountTax amountCalculated: read only
totalTotalCalculated: read only
amountPaidAmount paidNumber (USD)
balanceDueBalance dueCalculated: read only
paidDatePaid onDate, YYYY-MM-DD
paymentMethodPayment methodOne of: Bank transfer, Card, Direct debit, Cheque, Other
paymentReferencePayment referenceText
billingAddressBilling addressText
notesNotesText
documentsInvoice documentsFiles: read only, returned as [{ id, name, size }]
Invoice Lines — /api/v1/invoiceLines
KeyLabelFormat
invoiceInvoice (required)Link to a invoice: id, name or { id }; returned as { id, name }
descriptionDescription (required)Text
quantityQuantity (required)Number
unitPriceUnit price (required)Number (USD)
amountAmountCalculated: read only
Usage — /api/v1/usage
KeyLabelFormat
numberRecord numberAuto number: filled in when empty
clientClient (required)Link to a client: id, name or { id }; returned as { id, name }
metricMetric (required)Link to a usage metric: id, name or { id }; returned as { id, name }
dateDate (required)Date, YYYY-MM-DD
valueValue (required)Number
notesNotesText
identifiersUser IDsText
Usage Metrics — /api/v1/usageMetrics
KeyLabelFormat
nameMetric name (required)Text
unitUnitText
aggregationCombine daily values by (required)One of: Sum, Average, Maximum, Latest, Unique
descriptionDescriptionText
Conversations — /api/v1/conversations
KeyLabelFormat
subjectSubject (required)Text
clientClientLink to a client: id, name or { id }; returned as { id, name }
contactsContactsList of links to a contact: id, name or { id }; returned as { id, name }
dateDate (required)Date and time, ISO 8601
channelChannel (required)One of: Email, Phone, Video call, Chat, In person
directionDirectionOne of: Inbound, Outbound
sentimentSentimentOne of: Positive, Neutral, Negative
ownerOwnerPerson: { id } or an email or full name; returned as { id, name }
fromFromText
toToText
contentMessage / email contentText
summarySummaryText
nextStepsNext stepsText
Meetings — /api/v1/meetings
KeyLabelFormat
titleTitle (required)Text
clientClientLink to a client: id, name or { id }; returned as { id, name }
contactsContactsList of links to a contact: id, name or { id }; returned as { id, name }
startStarts (required)Date and time, ISO 8601
durationDuration (minutes)Number
typeTypeOne of: Kickoff, Check-in, QBR, Training, Escalation, Renewal
statusStatus (required)One of: Scheduled, Completed, Cancelled, No-show
locationLocation / linkText
ownerOwnerPerson: { id } or an email or full name; returned as { id, name }
agendaAgendaText
summarySummary: what was discussedText
outcomeDecisions & next stepsText
Tasks — /api/v1/tasks
KeyLabelFormat
subjectSubject (required)Text
clientClientLink to a client: id, name or { id }; returned as { id, name }
contactsContactsList of links to a contact: id, name or { id }; returned as { id, name }
dueDateDue dateDate, YYYY-MM-DD
statusStatus (required)One of: Not started, In progress, Waiting, Done
priorityPriorityOne of: Low, Normal, High, Urgent
assigneeAssigned toPerson: { id } or an email or full name; returned as { id, name }
dueStatusDue statusCalculated: read only
descriptionDescriptionText
playbookPlaybookLink to a playbook: id, name or { id }; returned as { id, name }
stepPlaybook stepLink to a playbook step: id, name or { id }; returned as { id, name }
isDoneDone (1/0)Calculated: read only
Playbooks — /api/v1/playbooks
KeyLabelFormat
namePlaybook name (required)Text
clientClient (required)Link to a client: id, name or { id }; returned as { id, name }
ownerOwnerPerson: { id } or an email or full name; returned as { id, name }
statusStatus (required)One of: Not started, In progress, On hold, Completed, Cancelled
startDateStart dateDate, YYYY-MM-DD
targetDateTarget dateDate, YYYY-MM-DD
taskCountTasksCalculated: read only
doneCountTasks doneCalculated: read only
progressProgressCalculated: read only
objectiveObjectiveText
templateTemplateLink to a playbook template: id, name or { id }; returned as { id, name }
sourceStartedText
triggerKeyTrigger keyText
Playbook Steps — /api/v1/playbookSteps
KeyLabelFormat
nameStep name (required)Text
playbookPlaybook (required)Link to a playbook: id, name or { id }; returned as { id, name }
orderOrderNumber
dueDateDue dateDate, YYYY-MM-DD
taskCountTasksCalculated: read only
doneCountTasks doneCalculated: read only
progressProgressCalculated: read only
descriptionDescriptionText
Playbook Templates — /api/v1/playbookTemplates
KeyLabelFormat
nameTemplate name (required)Text
activeActivetrue / false (CSV: yes/no, 1/0)
triggerTypeStarts (required)One of: Manually, When a field changes, Days before a date, When a client is created
triggerFieldTrigger fieldText
triggerValueTrigger valueText
triggerDaysDays beforeNumber
stepCountStepsCalculated: read only
taskCountTasksCalculated: read only
descriptionDescriptionText
Template Steps — /api/v1/templateSteps
KeyLabelFormat
nameStep name (required)Text
templateTemplate (required)Link to a playbook template: id, name or { id }; returned as { id, name }
orderOrderNumber
dueAfterDaysDue (days after start)Number
descriptionDescriptionText
Template Tasks — /api/v1/templateTasks
KeyLabelFormat
subjectSubject (required)Text
stepTemplate step (required)Link to a template step: id, name or { id }; returned as { id, name }
templateTemplate (required)Link to a playbook template: id, name or { id }; returned as { id, name }
dueAfterDaysDue (days after start)Number
priorityPriorityOne of: Low, Normal, High, Urgent
assignToAssign toOne of: Playbook owner, Client's account owner, Specific user
assigneeSpecific userPerson: { id } or an email or full name; returned as { id, name }
descriptionDescriptionText
Renewals — /api/v1/renewals
KeyLabelFormat
nameRenewal name (required)Text
clientClient (required)Link to a client: id, name or { id }; returned as { id, name }
renewalDateRenewal date (required)Date, YYYY-MM-DD
stageStage (required)One of: Upcoming, Proposal sent, Negotiation, Won, Lost
currentArrCurrent ARRNumber (USD)
proposedArrRenewal ARRNumber (USD)
probabilityProbabilityCalculated: read only
forecastForecast ARRCalculated: read only
changeARR changeCalculated: read only
daysToRenewalDays to renewalCalculated: read only
ownerOwnerPerson: { id } or an email or full name; returned as { id, name }
closedDateClosed dateDate, YYYY-MM-DD
lostReasonLost reasonOne of: Budget, Low adoption, Competitor, Champion left, Acquired / closed, Other
notesNotesText
Expansion — /api/v1/opportunities
KeyLabelFormat
nameOpportunity name (required)Text
clientClient (required)Link to a client: id, name or { id }; returned as { id, name }
typeTypeOne of: Upsell, Cross-sell, More seats, Services
stageStage (required)One of: Identified, Qualified, Proposal, Won, Lost
amountAdded ARRNumber (USD)
closeDateExpected closeDate, YYYY-MM-DD
probabilityProbabilityCalculated: read only
forecastForecast ARRCalculated: read only
ownerOwnerPerson: { id } or an email or full name; returned as { id, name }
descriptionDescriptionText
Surveys — /api/v1/surveys
KeyLabelFormat
numberResponse numberAuto number: filled in when empty
typeSurvey (required)One of: NPS, CSAT
contactContactLink to a contact: id, name or { id }; returned as { id, name }
clientClientLink to a client: id, name or { id }; returned as { id, name }
scoreScore (required)Number
categoryCategoryCalculated: read only
dateResponse date (required)Date, YYYY-MM-DD
commentCommentText
Goals — /api/v1/goals
KeyLabelFormat
nameGoal (required)Text
clientClient (required)Link to a client: id, name or { id }; returned as { id, name }
statusStatus (required)One of: Not started, On track, At risk, Achieved, Missed
metricSuccess measureText
baselineStarting valueNumber
targetTargetNumber
currentCurrent valueNumber
progressProgressCalculated: read only
targetDateTarget dateDate, YYYY-MM-DD
ownerOwnerPerson: { id } or an email or full name; returned as { id, name }
descriptionHow we get thereText

See also the CSV import guide.