Customers
List Customers
Retrieve a paginated list of customers. Supports filtering by search query, tags, and blacklist status.
GET /api/v1/public/customers
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | no | Number of customers to return (1-200, default 50) |
cursor | string | no | Customer ID for pagination (from next_cursor in previous response) |
search | string | no | Search by phone number, name, or email |
tags | string | no | Comma-separated list of tags to filter by |
blacklisted | string | no | Filter by blacklist status: "true" or "false" |
Example Request
- cURL
- JavaScript
- Python
curl -X GET "https://chat.api.co.id/api/v1/public/customers?limit=50&search=john&tags=vip,loyal" \
-H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
"https://chat.api.co.id/api/v1/public/customers?limit=50&search=john&tags=vip,loyal",
{
headers: {
"Authorization": "Bearer YOUR_API_KEY"
}
}
);
const { data, pagination } = await response.json();
import requests
response = requests.get(
"https://chat.api.co.id/api/v1/public/customers",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"limit": 50, "search": "john", "tags": "vip,loyal"}
)
result = response.json()
customers = result["data"]
pagination = result["pagination"]
Response
{
"success": true,
"data": [
{
"id": "clxxx1234567890",
"phone_number": "628123456789",
"name": "John Doe",
"email": "[email protected]",
"tags": ["vip", "loyal"],
"is_window_active": true,
"unread_count": 3,
"created_at": "2026-01-01T00:00:00.000Z"
},
{
"id": "clxxx0987654321",
"phone_number": "628987654321",
"name": "Jane Smith",
"email": "[email protected]",
"tags": ["new"],
"is_window_active": false,
"unread_count": 0,
"created_at": "2026-02-15T08:30:00.000Z"
}
],
"pagination": {
"has_more": true,
"next_cursor": "clxxx0987654321",
"limit": 50
}
}
Use next_cursor from the pagination object as the cursor query parameter to fetch the next page.
Get Customer
Retrieve full details for a single customer, including custom fields, consent logs, and notes.
GET /api/v1/public/customers/:id
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Customer ID (CUID), phone number, or Instagram username |
Example Request
- cURL
- JavaScript
- Python
curl -X GET "https://chat.api.co.id/api/v1/public/customers/628123456789" \
-H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
"https://chat.api.co.id/api/v1/public/customers/628123456789",
{
headers: {
"Authorization": "Bearer YOUR_API_KEY"
}
}
);
const { data } = await response.json();
console.log("Customer:", data.name);
import requests
response = requests.get(
"https://chat.api.co.id/api/v1/public/customers/628123456789",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()["data"]
print("Customer:", data["name"])
Response
{
"success": true,
"data": {
"id": "clxxx1234567890",
"phone_number": "628123456789",
"name": "John Doe",
"email": "[email protected]",
"instagram_username": null,
"tags": ["vip", "loyal"],
"is_window_active": true,
"unread_count": 3,
"custom_fields": {
"company": "Acme Inc",
"plan": "enterprise"
},
"consent_logs": [
{
"source": "website_form",
"purpose": "marketing",
"consent_status": true,
"logged_at": "2026-01-01T00:00:00.000Z"
}
],
"notes": [
{
"id": "note_abc123",
"content": "VIP customer, handle with care",
"created_at": "2026-01-05T14:00:00.000Z"
}
],
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-07T10:30:00.000Z"
}
}
Create Customer
Create a new customer record with phone number, profile information, tags, and consent status.
POST /api/v1/public/customers
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
phone_number | string | yes | Customer phone number in E.164 format (e.g., 628123456789) |
whatsapp_phone_number_id | string | yes | Which of your WhatsApp numbers to associate this customer with. Use the id from GET /phone-numbers. |
name | string | no | Customer display name |
email | string | no | Customer email address |
tags | string[] | no | Array of tags for categorization (e.g., ["vip", "loyal"]) |
consent_status | boolean | no | Whether the customer has given messaging consent |
consent_source | string | no | Source of consent (e.g., "api", "website_form", "manual") |
Example Request
- cURL
- JavaScript
- Python
curl -X POST "https://chat.api.co.id/api/v1/public/customers" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "628123456789",
"whatsapp_phone_number_id": "clyyy9876543210",
"name": "John Doe",
"email": "[email protected]",
"tags": ["vip"],
"consent_status": true,
"consent_source": "api"
}'
const response = await fetch("https://chat.api.co.id/api/v1/public/customers", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
phone_number: "628123456789",
whatsapp_phone_number_id: "clyyy9876543210",
name: "John Doe",
email: "[email protected]",
tags: ["vip"],
consent_status: true,
consent_source: "api"
})
});
const { data } = await response.json();
console.log("Created customer:", data.id);
import requests
response = requests.post(
"https://chat.api.co.id/api/v1/public/customers",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
json={
"phone_number": "628123456789",
"whatsapp_phone_number_id": "clyyy9876543210",
"name": "John Doe",
"email": "[email protected]",
"tags": ["vip"],
"consent_status": True,
"consent_source": "api"
}
)
data = response.json()["data"]
print("Created customer:", data["id"])
Response
{
"success": true,
"data": {
"id": "clxxx1234567890",
"phone_number": "628123456789",
"name": "John Doe",
"email": "[email protected]",
"tags": ["vip"],
"consent_status": true,
"consent_source": "api",
"blacklisted": false,
"created_at": "2026-04-03T10:00:00.000Z",
"updated_at": "2026-04-03T10:00:00.000Z"
}
}
Update Customer
Update an existing customer's profile. Only provided fields are modified (partial update).
PATCH /api/v1/public/customers/:id
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Customer ID (CUID) |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | no | Updated customer name |
email | string | no | Updated email address |
tags | string[] | no | Replaces all existing tags with this array |
Example Request
- cURL
- JavaScript
- Python
curl -X PATCH "https://chat.api.co.id/api/v1/public/customers/clxxx1234567890" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Name",
"tags": ["vip", "loyal"]
}'
const response = await fetch("https://chat.api.co.id/api/v1/public/customers/clxxx1234567890", {
method: "PATCH",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
name: "Updated Name",
tags: ["vip", "loyal"]
})
});
import requests
response = requests.patch(
"https://chat.api.co.id/api/v1/public/customers/clxxx1234567890",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
json={
"name": "Updated Name",
"tags": ["vip", "loyal"]
}
)
Response
{
"success": true,
"data": {
"id": "clxxx1234567890",
"phone_number": "628123456789",
"name": "Updated Name",
"email": "[email protected]",
"tags": ["vip", "loyal"],
"consent_status": true,
"blacklisted": false,
"updated_at": "2026-04-03T11:00:00.000Z"
}
}
Delete Customer
Permanently delete a customer record. This action cannot be undone.
DELETE /api/v1/public/customers/:id
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Customer ID (CUID) to delete |
Warning: Deleting a customer is permanent and will also remove all associated conversation history, notes, and consent records.
Example Request
- cURL
- JavaScript
- Python
curl -X DELETE "https://chat.api.co.id/api/v1/public/customers/clxxx1234567890" \
-H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch("https://chat.api.co.id/api/v1/public/customers/clxxx1234567890", {
method: "DELETE",
headers: {
"Authorization": "Bearer YOUR_API_KEY"
}
});
import requests
response = requests.delete(
"https://chat.api.co.id/api/v1/public/customers/clxxx1234567890",
headers={
"Authorization": "Bearer YOUR_API_KEY"
}
)
Response
{
"success": true,
"data": {
"id": "clxxx1234567890",
"deleted": true
}
}
Customer Consent
Log consent actions for a customer. Useful for GDPR/compliance tracking and managing messaging permissions.
POST /api/v1/public/customers/:id/consent
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Customer ID (CUID) |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | yes | Consent action: "OPT_IN", "OPT_OUT", or "UPDATE" |
source | string | yes | Where consent was collected (e.g., "website_form", "api", "phone_call") |
purpose | string | no | Purpose of consent (e.g., "marketing", "transactional", "support") |
Example Request
- cURL
- JavaScript
- Python
curl -X POST "https://chat.api.co.id/api/v1/public/customers/clxxx1234567890/consent" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "OPT_IN",
"source": "website_form",
"purpose": "marketing"
}'
const response = await fetch("https://chat.api.co.id/api/v1/public/customers/clxxx1234567890/consent", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
action: "OPT_IN",
source: "website_form",
purpose: "marketing"
})
});
import requests
response = requests.post(
"https://chat.api.co.id/api/v1/public/customers/clxxx1234567890/consent",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
json={
"action": "OPT_IN",
"source": "website_form",
"purpose": "marketing"
}
)
Response
{
"success": true,
"data": {
"customer_id": "clxxx1234567890",
"action": "OPT_IN",
"source": "website_form",
"purpose": "marketing",
"consent_status": true,
"logged_at": "2026-04-03T10:00:00.000Z"
}
}
Check Messaging Window
Check if the 24-hour messaging window is active for a customer. Non-template messages can only be sent within this window.
GET /api/v1/public/customers/:id/window-status
24-Hour Window Rule: WhatsApp only allows free-form messages (text, media, interactive) within 24 hours of the customer's last inbound message. After the window closes, you can only send pre-approved template messages. Always check the window status before sending non-template messages.
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Customer ID (CUID), phone number, or Instagram username |
Example Request
- cURL
- JavaScript
- Python
curl -X GET "https://chat.api.co.id/api/v1/public/customers/628123456789/window-status" \
-H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
"https://chat.api.co.id/api/v1/public/customers/628123456789/window-status",
{ headers: { "Authorization": "Bearer YOUR_API_KEY" } }
);
const { data } = await response.json();
if (data.whatsapp.is_window_active) {
// Can send free-form messages
} else {
// Must use template messages
}
response = requests.get(
"https://chat.api.co.id/api/v1/public/customers/628123456789/window-status",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()["data"]
if data["whatsapp"]["is_window_active"]:
# Can send free-form messages
else:
# Must use template messages
Response
{
"success": true,
"data": {
"customer_id": "clxxx1234567890",
"whatsapp": {
"is_window_active": true,
"window_expires_at": "2026-01-08T10:30:00.000Z",
"last_inbound_message_at": "2026-01-07T10:30:00.000Z",
"can_send_freeform": true,
"can_send_template": true,
"free_entry_point": {
"is_active": true,
"expires_at": "2026-01-14T10:30:00.000Z"
},
"is_free": true,
"free_reason": "ctwa",
"free_service_messages": {
"limit": 1000,
"used": 312,
"remaining": 688
}
},
"instagram": {
"is_window_active": false,
"window_expires_at": null
}
}
}
Click-to-WhatsApp Free Window
whatsapp.free_entry_point tells you whether the customer is inside a free entry point window from a Click-to-WhatsApp ad. Since October 2026 this window lasts up to 7 days, and every message sent to the customer inside it is free, including templates. It opens when the customer messages you from an ad. Messages sent inside this window do not count toward the 1,000 free service messages. expires_at is null if the customer never came from an ad.
This is an estimate: we start the window at the ad message, while Meta starts it at your first reply (within 24 hours). Your Meta invoice is the final number.
Is the Next Message Free?
whatsapp.is_free tells you whether the next message to this customer is free. free_reason says why:
"ctwa": the customer is inside a Click-to-WhatsApp free window. Every message is free, templates included."free_tier": the 24-hour window is open and the customer's phone number still has free service messages left this month. Free-form messages are free. Templates are still charged.null: the next message is charged, or the window is closed and you can only send a template.
free_service_messages shows the 1,000 free service messages used and left this UTC month on the phone number linked to the customer. It is null if the customer has no linked number. These numbers are estimates as well.
Toggle Blacklist
Add or remove a customer from the blacklist. Blacklisted customers will not receive any messages.
PATCH /api/v1/public/customers/:id/blacklist
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Customer ID (CUID), phone number, or Instagram username |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
blacklisted | boolean | yes | Set to true to blacklist, false to remove from blacklist |
Example Request
- cURL
- JavaScript
- Python
curl -X PATCH "https://chat.api.co.id/api/v1/public/customers/628123456789/blacklist" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"blacklisted": true}'
const response = await fetch(
"https://chat.api.co.id/api/v1/public/customers/628123456789/blacklist",
{
method: "PATCH",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({ blacklisted: true })
}
);
const { data } = await response.json();
console.log("Blacklisted:", data.blacklisted);
import requests
response = requests.patch(
"https://chat.api.co.id/api/v1/public/customers/628123456789/blacklist",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
json={"blacklisted": True}
)
data = response.json()["data"]
print("Blacklisted:", data["blacklisted"])
Response
{
"success": true,
"data": {
"id": "clxxx1234567890",
"phone_number": "628123456789",
"name": "John Doe",
"blacklisted": true,
"updated_at": "2026-04-03T10:00:00.000Z"
}
}
Customer Notes
Manage internal notes attached to a customer. Notes are visible only to your team.
GET /api/v1/public/customers/:id/notes
POST /api/v1/public/customers/:id/notes
DELETE /api/v1/public/customers/:id/notes/:noteId
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Customer ID (CUID), phone number, or Instagram username |
noteId | string | yes | Note ID (required for DELETE only) |
Body Parameters (POST)
| Parameter | Type | Required | Description |
|---|---|---|---|
content | string | yes | The note content text |
List Notes
- cURL
- JavaScript
- Python
curl -X GET "https://chat.api.co.id/api/v1/public/customers/628123456789/notes" \
-H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
"https://chat.api.co.id/api/v1/public/customers/628123456789/notes",
{
headers: {
"Authorization": "Bearer YOUR_API_KEY"
}
}
);
const { data } = await response.json();
console.log("Notes:", data);
import requests
response = requests.get(
"https://chat.api.co.id/api/v1/public/customers/628123456789/notes",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
notes = response.json()["data"]
print("Notes:", notes)
List Notes Response
{
"success": true,
"data": [
{
"id": "note_abc123",
"content": "VIP customer, handle with care",
"created_by": "[email protected]",
"created_at": "2026-01-05T14:00:00.000Z"
},
{
"id": "note_def456",
"content": "Prefers communication in English",
"created_by": "[email protected]",
"created_at": "2026-02-10T09:15:00.000Z"
}
]
}
Add Note
- cURL
- JavaScript
- Python
curl -X POST "https://chat.api.co.id/api/v1/public/customers/628123456789/notes" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Follow up on order #1234"}'
const response = await fetch(
"https://chat.api.co.id/api/v1/public/customers/628123456789/notes",
{
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({ content: "Follow up on order #1234" })
}
);
const { data } = await response.json();
console.log("Note created:", data.id);
import requests
response = requests.post(
"https://chat.api.co.id/api/v1/public/customers/628123456789/notes",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
json={"content": "Follow up on order #1234"}
)
data = response.json()["data"]
print("Note created:", data["id"])
Add Note Response
{
"success": true,
"data": {
"id": "note_ghi789",
"content": "Follow up on order #1234",
"created_by": "[email protected]",
"created_at": "2026-04-03T10:00:00.000Z"
}
}
Delete Note
- cURL
- JavaScript
- Python
curl -X DELETE "https://chat.api.co.id/api/v1/public/customers/628123456789/notes/note_abc123" \
-H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
"https://chat.api.co.id/api/v1/public/customers/628123456789/notes/note_abc123",
{
method: "DELETE",
headers: {
"Authorization": "Bearer YOUR_API_KEY"
}
}
);
const { success } = await response.json();
console.log("Deleted:", success);
import requests
response = requests.delete(
"https://chat.api.co.id/api/v1/public/customers/628123456789/notes/note_abc123",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
print("Deleted:", response.json()["success"])
Delete Note Response
{
"success": true,
"message": "Note deleted successfully"
}
Try it out
Press Try it and paste your API key (kc_live_...) into the API key (Bearer) field. Get the key from API Keys. Requests run against your live account, so endpoints that send, create, or delete act on real data.
- GET/api/v1/public/customersList Customers
- POST/api/v1/public/customersCreate Customer
- GET/api/v1/public/customers/{id}Get Customer
- PATCH/api/v1/public/customers/{id}Update Customer
- DELETE/api/v1/public/customers/{id}Delete Customer
- POST/api/v1/public/customers/{id}/consentCustomer Consent
- GET/api/v1/public/customers/{id}/window-statusCheck Messaging Window
- PATCH/api/v1/public/customers/{id}/blacklistToggle Blacklist
- GET/api/v1/public/customers/{id}/notesList Notes
- POST/api/v1/public/customers/{id}/notesAdd Note
- DELETE/api/v1/public/customers/{id}/notes/{noteId}Delete Note