Skip to main content

Customers

List customers​

GET /api/customers · scope customers:read

Takes search, limit, offset.

{
"customers": [
{
"id": 87,
"name": "Priya Kumar",
"phone": "+918888888888",
"email": "[email protected]",
"company": "Pragati Interiors",
"notes": "Renewal due in November.",
"created_at": "2026-08-02T10:00:00Z"
}
],
"total": 240
}

Get a customer​

GET /api/customers/{id} · scope customers:read

Returns the customer with their history — what a rep needs on screen before the call connects:

{
"id": 87,
"name": "Priya Kumar",
"phone": "+918888888888",
"company": "Pragati Interiors",
"memory": { "prefers_evening_calls": true, "last_objection": "price" },
"conversations": [
{
"uuid": "7f3a1b2c-…",
"call_type": "outbound",
"agent_type": "human",
"agent_name": "Arjun Rao",
"status": "completed",
"started_at": "2026-09-20T14:32:01Z",
"answered_at": "2026-09-20T14:32:09Z",
"ended_at": "2026-09-20T14:33:54Z",
"recording": "https://…/recording.mp3",
"transcript": [
{ "speaker": "agent", "text": "Hi Priya, Arjun here." }
]
}
]
}

memory is what previous calls established about this person. conversations is every prior call with its transcript. Both are omitted entirely when there is nothing to report — a customer with no call history returns neither key, rather than null or []. Check for the key before reading it. They never appear on list rows, only here.

Create a customer​

POST /api/customers · scope customers:write

{
"name": "Priya Kumar",
"phone": "+918888888888",
"email": "[email protected]",
"company": "Pragati Interiors",
"notes": "Renewal due in November."
}

Only phone is required — it is the identity here, since it is what an incoming call is matched on.

Open to every role: creating a customer is day-to-day sales work, not configuration.

Update a customer​

PATCH /api/customers/{id} · scope customers:write

Takes the same body as create, and phone is required here too:

{
"name": "Priya Kumar",
"phone": "+918888888888",
"email": "[email protected]",
"company": "Pragati Interiors",
"notes": "Renewal due in November."
}
Send the whole customer, not just the changed field

Despite the PATCH verb this is a full replace. Every field is written on every call, so any field you leave out is set to null — sending only {"phone": "+91…"} to correct a number clears that customer's name, email, company and notes.

Read the customer first, change the field you mean, and send all five back.

Returns 200 with the updated customer.

StatusCause
400phone missing, or not matching +? followed by 6–20 digits
404{"detail": "customer not found"}
409{"detail": "another customer already has this phone number"}

Delete a customer​

DELETE /api/customers/{id} — manager or above

Deleting takes the call history with it, which is why it sits behind a role rather than a scope.

Resolve a customer​

POST /api/customers/resolve · scope customers:write

Maps an external CRM record onto a customer here, creating one if the phone number is new. This is what a Salesforce side panel calls when a rep opens a lead — it needs a customer to attach the call to, and it should not care whether one already existed.

{
"phone": "+918888888888",
"name": "Priya Kumar",
"email": "[email protected]",
"company": "Pragati Interiors",
"external_ref": "003XX000004TmiQ"
}

Returns the resolved customer, existing or newly created.

It needs customers:write rather than :read precisely because it can create — the alternative would be a read scope that quietly writes.

Route ordering

/customers/resolve is registered before /customers/:id, so "resolve" is never parsed as an id. Worth knowing if you add routes: the more specific path has to come first.

Scopes and roles​

ActionRequires
List, getcustomers:read
Create, update, resolvecustomers:write
Deletemanager role — API keys are refused