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 100offset: Records to skip, default 0updated_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
| Key | Label | Format |
|---|
clientNumber | Client number | Auto number: filled in when empty |
name | Client name (required) | Text |
industry | Industry | Text |
website | Website | Web address |
tier | Tier | One of: Enterprise, Mid-Market, SMB |
health | Health | One of: Healthy, At Risk, Critical |
arr | ARR | Number (USD) |
renewalDate | Renewal date | Date, YYYY-MM-DD |
notes | Notes | Text |
owner | Account owner (CSM) | Person: { id } or an email or full name; returned as { id, name } |
daysToRenewal | Days to renewal | Calculated: read only |
documents | Documents | Files: read only, returned as [{ id, name, size }] |
openBalance | Open balance | Calculated: read only |
healthScore | Health score | Number |
Contacts — /api/v1/contacts
| Key | Label | Format |
|---|
firstName | First name (required) | Text |
lastName | Last name | Text |
role | Title | Text |
client | Client | Link to a client: id, name or { id }; returned as { id, name } |
isPrimary | Primary contact | true / false (CSV: yes/no, 1/0) |
email | Email | Email address |
phone | Phone | Text |
notes | Notes | Text |
clientHealth | Client health | Calculated: read only |
Billing — /api/v1/billing
| Key | Label | Format |
|---|
invoiceNumber | Invoice number | Auto number: filled in when empty |
client | Client (required) | Link to a client: id, name or { id }; returned as { id, name } |
billingContact | Billing contact | Link to a contact: id, name or { id }; returned as { id, name } |
status | Status (required) | One of: Draft, Pending to send, Sent, Void |
paymentStatus | Payment status | Calculated: read only |
poNumber | PO number | Text |
issueDate | Issue date | Date, YYYY-MM-DD |
paymentTerms | Payment terms | One of: Due on receipt, Net 15, Net 30, Net 45, Net 60 |
dueDate | Due date | Calculated: read only |
daysOverdue | Days overdue | Calculated: read only |
sentDate | Sent on | Date, YYYY-MM-DD |
periodStart | Service period start | Date, YYYY-MM-DD |
periodEnd | Service period end | Date, YYYY-MM-DD |
subtotal | Subtotal | Calculated: read only |
discount | Discount | Number (USD) |
taxRate | Tax rate | Number, e.g. 12.5 for 12.5% |
taxAmount | Tax amount | Calculated: read only |
total | Total | Calculated: read only |
amountPaid | Amount paid | Number (USD) |
balanceDue | Balance due | Calculated: read only |
paidDate | Paid on | Date, YYYY-MM-DD |
paymentMethod | Payment method | One of: Bank transfer, Card, Direct debit, Cheque, Other |
paymentReference | Payment reference | Text |
billingAddress | Billing address | Text |
notes | Notes | Text |
documents | Invoice documents | Files: read only, returned as [{ id, name, size }] |
Invoice Lines — /api/v1/invoiceLines
| Key | Label | Format |
|---|
invoice | Invoice (required) | Link to a invoice: id, name or { id }; returned as { id, name } |
description | Description (required) | Text |
quantity | Quantity (required) | Number |
unitPrice | Unit price (required) | Number (USD) |
amount | Amount | Calculated: read only |
Usage — /api/v1/usage
| Key | Label | Format |
|---|
number | Record number | Auto number: filled in when empty |
client | Client (required) | Link to a client: id, name or { id }; returned as { id, name } |
metric | Metric (required) | Link to a usage metric: id, name or { id }; returned as { id, name } |
date | Date (required) | Date, YYYY-MM-DD |
value | Value (required) | Number |
notes | Notes | Text |
identifiers | User IDs | Text |
Usage Metrics — /api/v1/usageMetrics
| Key | Label | Format |
|---|
name | Metric name (required) | Text |
unit | Unit | Text |
aggregation | Combine daily values by (required) | One of: Sum, Average, Maximum, Latest, Unique |
description | Description | Text |
Conversations — /api/v1/conversations
| Key | Label | Format |
|---|
subject | Subject (required) | Text |
client | Client | Link to a client: id, name or { id }; returned as { id, name } |
contacts | Contacts | List of links to a contact: id, name or { id }; returned as { id, name } |
date | Date (required) | Date and time, ISO 8601 |
channel | Channel (required) | One of: Email, Phone, Video call, Chat, In person |
direction | Direction | One of: Inbound, Outbound |
sentiment | Sentiment | One of: Positive, Neutral, Negative |
owner | Owner | Person: { id } or an email or full name; returned as { id, name } |
from | From | Text |
to | To | Text |
content | Message / email content | Text |
summary | Summary | Text |
nextSteps | Next steps | Text |
Meetings — /api/v1/meetings
| Key | Label | Format |
|---|
title | Title (required) | Text |
client | Client | Link to a client: id, name or { id }; returned as { id, name } |
contacts | Contacts | List of links to a contact: id, name or { id }; returned as { id, name } |
start | Starts (required) | Date and time, ISO 8601 |
duration | Duration (minutes) | Number |
type | Type | One of: Kickoff, Check-in, QBR, Training, Escalation, Renewal |
status | Status (required) | One of: Scheduled, Completed, Cancelled, No-show |
location | Location / link | Text |
owner | Owner | Person: { id } or an email or full name; returned as { id, name } |
agenda | Agenda | Text |
summary | Summary: what was discussed | Text |
outcome | Decisions & next steps | Text |
Tasks — /api/v1/tasks
| Key | Label | Format |
|---|
subject | Subject (required) | Text |
client | Client | Link to a client: id, name or { id }; returned as { id, name } |
contacts | Contacts | List of links to a contact: id, name or { id }; returned as { id, name } |
dueDate | Due date | Date, YYYY-MM-DD |
status | Status (required) | One of: Not started, In progress, Waiting, Done |
priority | Priority | One of: Low, Normal, High, Urgent |
assignee | Assigned to | Person: { id } or an email or full name; returned as { id, name } |
dueStatus | Due status | Calculated: read only |
description | Description | Text |
playbook | Playbook | Link to a playbook: id, name or { id }; returned as { id, name } |
step | Playbook step | Link to a playbook step: id, name or { id }; returned as { id, name } |
isDone | Done (1/0) | Calculated: read only |
Playbooks — /api/v1/playbooks
| Key | Label | Format |
|---|
name | Playbook name (required) | Text |
client | Client (required) | Link to a client: id, name or { id }; returned as { id, name } |
owner | Owner | Person: { id } or an email or full name; returned as { id, name } |
status | Status (required) | One of: Not started, In progress, On hold, Completed, Cancelled |
startDate | Start date | Date, YYYY-MM-DD |
targetDate | Target date | Date, YYYY-MM-DD |
taskCount | Tasks | Calculated: read only |
doneCount | Tasks done | Calculated: read only |
progress | Progress | Calculated: read only |
objective | Objective | Text |
template | Template | Link to a playbook template: id, name or { id }; returned as { id, name } |
source | Started | Text |
triggerKey | Trigger key | Text |
Playbook Steps — /api/v1/playbookSteps
| Key | Label | Format |
|---|
name | Step name (required) | Text |
playbook | Playbook (required) | Link to a playbook: id, name or { id }; returned as { id, name } |
order | Order | Number |
dueDate | Due date | Date, YYYY-MM-DD |
taskCount | Tasks | Calculated: read only |
doneCount | Tasks done | Calculated: read only |
progress | Progress | Calculated: read only |
description | Description | Text |
Playbook Templates — /api/v1/playbookTemplates
| Key | Label | Format |
|---|
name | Template name (required) | Text |
active | Active | true / false (CSV: yes/no, 1/0) |
triggerType | Starts (required) | One of: Manually, When a field changes, Days before a date, When a client is created |
triggerField | Trigger field | Text |
triggerValue | Trigger value | Text |
triggerDays | Days before | Number |
stepCount | Steps | Calculated: read only |
taskCount | Tasks | Calculated: read only |
description | Description | Text |
Template Steps — /api/v1/templateSteps
| Key | Label | Format |
|---|
name | Step name (required) | Text |
template | Template (required) | Link to a playbook template: id, name or { id }; returned as { id, name } |
order | Order | Number |
dueAfterDays | Due (days after start) | Number |
description | Description | Text |
Template Tasks — /api/v1/templateTasks
| Key | Label | Format |
|---|
subject | Subject (required) | Text |
step | Template step (required) | Link to a template step: id, name or { id }; returned as { id, name } |
template | Template (required) | Link to a playbook template: id, name or { id }; returned as { id, name } |
dueAfterDays | Due (days after start) | Number |
priority | Priority | One of: Low, Normal, High, Urgent |
assignTo | Assign to | One of: Playbook owner, Client's account owner, Specific user |
assignee | Specific user | Person: { id } or an email or full name; returned as { id, name } |
description | Description | Text |
Renewals — /api/v1/renewals
| Key | Label | Format |
|---|
name | Renewal name (required) | Text |
client | Client (required) | Link to a client: id, name or { id }; returned as { id, name } |
renewalDate | Renewal date (required) | Date, YYYY-MM-DD |
stage | Stage (required) | One of: Upcoming, Proposal sent, Negotiation, Won, Lost |
currentArr | Current ARR | Number (USD) |
proposedArr | Renewal ARR | Number (USD) |
probability | Probability | Calculated: read only |
forecast | Forecast ARR | Calculated: read only |
change | ARR change | Calculated: read only |
daysToRenewal | Days to renewal | Calculated: read only |
owner | Owner | Person: { id } or an email or full name; returned as { id, name } |
closedDate | Closed date | Date, YYYY-MM-DD |
lostReason | Lost reason | One of: Budget, Low adoption, Competitor, Champion left, Acquired / closed, Other |
notes | Notes | Text |
Expansion — /api/v1/opportunities
| Key | Label | Format |
|---|
name | Opportunity name (required) | Text |
client | Client (required) | Link to a client: id, name or { id }; returned as { id, name } |
type | Type | One of: Upsell, Cross-sell, More seats, Services |
stage | Stage (required) | One of: Identified, Qualified, Proposal, Won, Lost |
amount | Added ARR | Number (USD) |
closeDate | Expected close | Date, YYYY-MM-DD |
probability | Probability | Calculated: read only |
forecast | Forecast ARR | Calculated: read only |
owner | Owner | Person: { id } or an email or full name; returned as { id, name } |
description | Description | Text |
Surveys — /api/v1/surveys
| Key | Label | Format |
|---|
number | Response number | Auto number: filled in when empty |
type | Survey (required) | One of: NPS, CSAT |
contact | Contact | Link to a contact: id, name or { id }; returned as { id, name } |
client | Client | Link to a client: id, name or { id }; returned as { id, name } |
score | Score (required) | Number |
category | Category | Calculated: read only |
date | Response date (required) | Date, YYYY-MM-DD |
comment | Comment | Text |
Goals — /api/v1/goals
| Key | Label | Format |
|---|
name | Goal (required) | Text |
client | Client (required) | Link to a client: id, name or { id }; returned as { id, name } |
status | Status (required) | One of: Not started, On track, At risk, Achieved, Missed |
metric | Success measure | Text |
baseline | Starting value | Number |
target | Target | Number |
current | Current value | Number |
progress | Progress | Calculated: read only |
targetDate | Target date | Date, YYYY-MM-DD |
owner | Owner | Person: { id } or an email or full name; returned as { id, name } |
description | How we get there | Text |
See also the CSV import guide.