Skip to main content

Customers

The people being called. Campaigns select them by tag; inbound calls match them by number so a returning caller is recognised.

List customers​

GET /api/voice/customers?search=priya&tags=winback,north&limit=50&offset=0

ParamNotes
searchMatches name, phone, or email
tagsComma-separated or repeated. Matches any of them
limit / offsetDefault 50 / 0
{
"customers": [
{
"uuid": "c1d2e3f4-a5b6-7890-abcd-ef1234567890",
"name": "Priya Kumar",
"phone": "+918888888888",
"email": "[email protected]",
"tags": ["winback", "north"],
"preferred_language": "hi-IN",
"metadata": { "cart_value": 850, "product_interest": "Standing Desk Pro" },
"total_calls": 3,
"last_contact": "2026-09-20T09:15:44Z",
"created_at": "2026-08-02T10:00:00Z",
"updated_at": "2026-09-20T09:15:44Z"
}
],
"total": 240
}

Ordered by last_contact descending, so whoever was spoken to most recently is first.

Get a customer​

GET /api/voice/customers/{uuid}

Create a customer​

POST /api/voice/customers

{
"name": "Priya Kumar",
"phone": "+918888888888",
"email": "[email protected]",
"tags": ["winback", "north"],
"preferred_language": "hi-IN",
"metadata": { "cart_value": 850 }
}
FieldRequiredNotes
name✓
phone✓*E.164. *Either phone or email is required
email✓*
tags—Drive campaign targeting and visibility scoping
preferred_language—Overrides the agent's language for this person
metadata—Arbitrary JSON, substitutable into scripts
StatusCause
400{"detail": "name is required"}
400{"detail": "provide a phone number or an email"}
Duplicate numbers

Uniqueness is on the exact phone string, while call matching uses the last ten digits. +918888888888 and 8888888888 are stored as two customers, and an inbound call attaches to whichever the query returns first. Normalise to E.164 before importing.

Re-adding an exact duplicate returns 500 {"detail": "internal error"} rather than a 409.

Update a customer​

PATCH /api/voice/customers/{uuid}

Same fields; send only what changes.

note

Fields are applied with COALESCE, so email and preferred_language cannot be cleared once set — only replaced. Sending "tags": [] does clear tags.

Delete a customer​

DELETE /api/voice/customers/{uuid} → 204

Bulk import​

POST /api/voice/customers/bulk

{
"customers": [
{ "name": "Priya Kumar", "phone": "+918888888888", "tags": ["winback"] },
{ "name": "Arjun Rao", "phone": "+919999999999", "tags": ["winback"] }
]
}

At most 1000 per request. Upserts on (organization_id, phone), so re-importing a list updates rather than duplicates.

{
"imported": 898,
"failed": [
{ "index": 41, "detail": "name and phone are required" },
{ "index": 502, "detail": "could not be saved" }
]
}

Partial success is reported rather than rolled back: a 900-row import with two bad rows lands the 898, and the response names which failed so they can be corrected and resent. index is the position in the array you sent.

Both name and phone are required for bulk rows — unlike single creation, email-only contacts are rejected.

Tags​

GET /api/voice/customers/meta/tags

{ "tags": ["north", "south", "winback"] }

Every distinct tag in use, for populating a filter.

Tags do two jobs:

  1. Campaign targeting — an outbound flow's branches selects by tag
  2. Visibility scoping — a restricted user sees only customers carrying their assigned tags, and only the calls belonging to those customers

Customer memory​

GET /api/voice/customers/{uuid}/memory

Returns the whole per-scope memory map as stored — what the agent has retained about this person across calls. Unfiltered by design: this is the operator's complete view, which is what a privacy request needs.

{
"sales": { "prefers_evening_calls": true, "last_objection": "price" }
}

Click-to-call​

POST /api/voice/customers/{uuid}/calls

{
"call_agent_uuid": "us1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
"datasource_ids": ["kb1a2b3c-…"]
}
FieldNotes
call_agent_uuidThe user UUID of the teammate who should take the call
datasource_idsKnowledge base documents available for live assistance

Dials this customer from a human teammate's phone rather than the AI agent — that teammate's own number rings first, then the customer. They must have a phone number mapped; see Telephony.

Access scoping​

CallerSees
UnrestrictedEvery customer in the organization
Tag-restrictedOnly customers carrying at least one assigned tag
No tags assigned and restrictedNothing

The scope is applied in SQL, so a restricted user's pagination and totals are correct rather than short.