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."
}
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.
| Status | Cause |
|---|---|
400 | phone 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.
/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
| Action | Requires |
|---|---|
| List, get | customers:read |
| Create, update, resolve | customers:write |
| Delete | manager role — API keys are refused |