Contacts API
List, add, update and import the people you talk to.
These endpoints use your API key and return the same data as the Contacts page in the dashboard.
import { KerAbieClient } from '@kerabie/sdk';
const kerabie = new KerAbieClient({ apiKey: process.env.KERABIE_API_KEY });A contact is anyone with at least a name, email or phone number. Website visitors who only browsed are not contacts until they message you or you save them.
List contacts
GET /v1/contactsconst { contacts, meta } = await kerabie.contacts.list({ type: 'VIP', sort: 'recent' });| Query param | Description |
|---|---|
search | Case-insensitive match on name, email, phone, company or a tag name. |
type | LEAD, CUSTOMER or VIP. |
tagId | Only contacts with this tag. |
ownerId | Only contacts owned by this team member. |
sort | recent (default: last contact first, contacts who have never messaged last), newest or name. |
page, limit | Pagination. limit defaults to 20, maximum 100. |
{
"contacts": [
{
"id": 24,
"name": "Chidi Okafor",
"email": "chidi@palmex.com",
"phone": "+234 803 555 0142",
"company": "Palmex",
"notes": "Prefers WhatsApp.",
"contactType": "VIP",
"ownerId": 7,
"owner": { "id": 7, "user": { "fname": "Amara", "lname": "Osei", "email": "amara@kerabie.com", "avatar": null } },
"tagLinks": [{ "tag": { "id": 3, "name": "Enterprise", "color": null } }],
"visitorCountry": "NG",
"externalSource": null,
"lastContactAt": "2026-10-05T10:52:00.000Z",
"createdAt": "2026-09-28T09:10:00.000Z",
"_count": { "conversations": 3 }
}
],
"meta": { "total": 9, "page": 1, "limit": 20, "pages": 1 }
}lastContactAt is the last message in either direction (replies from the AI assistant don't count). For contacts created before this was tracked it falls back to their latest conversation.
Overview numbers
GET /v1/contacts/stats{ "total": 9, "newThisWeek": 2, "leads": 2, "vip": 2 }newThisWeek counts contacts added in the last 7 days. Results are cached for up to a minute and refresh immediately when contacts change.
Get a contact
GET /v1/contacts/:idReturns the contact with its last 10 conversations. 404 if it isn't in your organisation.
Add a contact
POST /v1/contactsconst { contact } = await kerabie.contacts.create({ name: 'Chidi Okafor', email: 'chidi@palmex.com', contactType: 'CUSTOMER' });| Body field | Notes |
|---|---|
name, email, phone | At least one is required. |
company, notes | Optional. Notes up to 5,000 characters. |
contactType | LEAD (default), CUSTOMER or VIP. |
ownerId | A team-member id in your organisation, or null. |
| Status | When |
|---|---|
400 | Nothing identifying was given, the email is malformed, the type is unknown, or the owner isn't on your team. |
409 | A contact with this email (any casing) already exists. |
Update a contact
PATCH /v1/contacts/:idSend only the fields you want to change. A blank text field clears it, and ownerId: null removes the owner. The same validation and 409 rule as adding applies: you can't give a contact an email another contact already has.
Import contacts
POST /v1/contacts/importconst result = await kerabie.contacts.import([
{ name: 'Sam Adeyemi', email: 'sam@tenda.ng', company: 'Tenda', type: 'lead' },
{ email: 'not-an-email' },
]);{ "created": 1, "duplicates": 0, "invalid": [{ "row": 2, "error": "\"not-an-email\" is not a valid email" }], "total": 2 }- Up to 1,000 rows per call.
- Each row needs a name or an email.
typeislead,customerorvip(any case) and defaults to lead. - Rows whose email already belongs to a contact are skipped and counted in
duplicates. The same email twice in one request is reported as invalid on the second row. - One bad row never fails the others; invalid rows are listed with their number (starting at 1).