API reference
Inbox
Conversations with customers through the chat widget, WhatsApp and email, with their messages. Follow along, reply on behalf of the company and close a conversation.
Endpoints
-
GET
/conversationsList conversations -
GET
/conversations/{conversation}Get conversation -
GET
/conversations/{conversation}/messagesList messages -
POST
/conversations/{conversation}/messagesSend message -
POST
/conversations/{conversation}/closeClose conversation
List conversations
/api/v1/conversations
A list of conversations, most recent message first. Filter by status, channel, customer or change date. With filter[status]=open you get what is still open.
- Scope
-
messages.read— Read conversations and messages from the inbox - Required feature
chat_widget|email|whatsapp
Query parameters
| Name | Type | Description |
|---|---|---|
limit
|
integer | Number of results per page. from 1 to 100 · default: 50 |
cursor
|
string | The next_cursor or prev_cursor from meta of the previous response. |
sort
|
string | Sort by created_at or updated_at; a leading minus sign sorts descending. one of: -last_message_at, last_message_at, -created_at, created_at, -updated_at, updated_at · default: -last_message_at |
filter[status]
|
string | Only conversations with this status: open, waiting_on_customer, waiting_on_team, snoozed or closed. one of: open, waiting_on_customer, waiting_on_team, snoozed, closed |
filter[channel]
|
string | Only conversations on this channel: web (chat widget), whatsapp or email. one of: web, whatsapp, email |
filter[customer_id]
|
string (uuid) | Only what belongs to this customer. |
filter[updated_since]
|
string (date-time) | Only what changed since this moment: ISO 8601 with a time zone, for example 2026-09-14T10:15:00Z. Useful for synchronising. |
Example request
curl "https://app.klantly.com/api/v1/conversations?filter[status]=open&sort=-last_message_at" \
-H "Authorization: Bearer $KLANTLY_API_KEY"$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('GET', 'conversations', [
'query' => [
'filter[status]' => 'open',
'sort' => '-last_message_at',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/conversations?filter[status]=open&sort=-last_message_at', {
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
},
});
const { data } = await response.json();import os
import requests
response = requests.get(
"https://app.klantly.com/api/v1/conversations",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
params={
"filter[status]": "open",
"sort": "-last_message_at"
},
)
data = response.json()["data"]Response 200
The response is a list with cursor pagination: data contains the objects, meta the pagination.
{
"data": [
{
"object": "conversation",
"id": "9d3f9a12-6b7c-4d8e-9f01-a2b3c4d5e6f1",
"status": "open",
"channel": "web",
"priority": "normal",
"subject": "Vraag over mijn offerte",
"summary": null,
"page_url": "https://example.com/verandas",
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"name": "Jan de Vries",
"email": "jan@example.com",
"phone": "+31 6 12345678"
},
"assigned_user_id": null,
"quote_id": null,
"invoice_id": null,
"appointment_id": null,
"message_count": 4,
"last_message_at": null,
"closed_at": null,
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
],
"meta": {
"limit": 50,
"next_cursor": "eyJpZCI6IjlkM2Y2YzFlIn0",
"prev_cursor": null
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
422
validation_failed— The input is invalid.
Get conversation
/api/v1/conversations/{conversation}
One conversation by id, with the customer details, the channel and the number of messages.
- Scope
-
messages.read— Read conversations and messages from the inbox - Required feature
chat_widget|email|whatsapp
Path parameters
| Name | Type | Description |
|---|---|---|
conversation required |
string (uuid) | The id (UUID) of the conversation. |
Example request
curl "https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
-H "Authorization: Bearer $KLANTLY_API_KEY"$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('GET', 'conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
},
});
const { data } = await response.json();import os
import requests
response = requests.get(
"https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
data = response.json()["data"]Response 200
{
"data": {
"object": "conversation",
"id": "9d3f9a12-6b7c-4d8e-9f01-a2b3c4d5e6f1",
"status": "open",
"channel": "web",
"priority": "normal",
"subject": "Vraag over mijn offerte",
"summary": null,
"page_url": "https://example.com/verandas",
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"name": "Jan de Vries",
"email": "jan@example.com",
"phone": "+31 6 12345678"
},
"assigned_user_id": null,
"quote_id": null,
"invoice_id": null,
"appointment_id": null,
"message_count": 4,
"last_message_at": null,
"closed_at": null,
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
404
not_found— Not found.
List messages
/api/v1/conversations/{conversation}/messages
The messages of a conversation, oldest first, so you read the conversation from top to bottom. With sort=-created_at you get the newest first.
- Scope
-
messages.read— Read conversations and messages from the inbox - Required feature
chat_widget|email|whatsapp
Path parameters
| Name | Type | Description |
|---|---|---|
conversation required |
string (uuid) | The id (UUID) of the conversation. |
Query parameters
| Name | Type | Description |
|---|---|---|
limit
|
integer | Number of results per page. from 1 to 100 · default: 50 |
cursor
|
string | The next_cursor or prev_cursor from meta of the previous response. |
sort
|
string | created_at (default) reads the conversation from top to bottom, -created_at shows the newest first. one of: created_at, -created_at · default: created_at |
Example request
curl "https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/messages" \
-H "Authorization: Bearer $KLANTLY_API_KEY"$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('GET', 'conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/messages');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/messages', {
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
},
});
const { data } = await response.json();import os
import requests
response = requests.get(
"https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/messages",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
data = response.json()["data"]Response 200
The response is a list with cursor pagination: data contains the objects, meta the pagination.
{
"data": [
{
"object": "message",
"id": "9d3f9b23-7c8d-4e9f-8012-b3c4d5e6f7a2",
"conversation_id": "9d3f9a12-6b7c-4d8e-9f01-a2b3c4d5e6f1",
"sender": "customer",
"user_id": null,
"body": "Kan de monteur morgen langskomen?",
"email_subject": null,
"attachment": {
"name": "offerte.pdf",
"mime_type": "application/pdf",
"size": 24680
},
"whatsapp_status": null,
"created_at": "2026-09-14T10:15:00Z"
}
],
"meta": {
"limit": 50,
"next_cursor": "eyJpZCI6IjlkM2Y2YzFlIn0",
"prev_cursor": null
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
404
not_found— Not found. -
422
validation_failed— The input is invalid.
Send message
/api/v1/conversations/{conversation}/messages
Sends a message to the customer over the channel of the conversation: chat, WhatsApp or email. If the channel does not accept it, nothing is stored and you get 409 conflict — on WhatsApp the 24-hour window has usually expired. Replying requires the feature of that channel, and the AI agent stops on this conversation, just like when a colleague replies. Outgoing messages have an anti-abuse limit: at most 250 per hour per company (shared with quote and invoice email) and at most 20 per hour in the same conversation through the API. Above that you get 429 rate_limited.
- Scope
-
messages.send— Send messages to customers and close conversations - Required feature
chat_widget|email|whatsapp
Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.
Path parameters
| Name | Type | Description |
|---|---|---|
conversation required |
string (uuid) | The id (UUID) of the conversation. |
Body (JSON)
| Field | Type | Description |
|---|---|---|
body
required
|
string | The text of the message. at most 10000 characters |
Example request
curl -X POST "https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/messages" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
-d '{
"body": "Dag Jan, we komen morgen tussen 9 en 11 uur langs."
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/messages', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
'json' => [
'body' => 'Dag Jan, we komen morgen tussen 9 en 11 uur langs.',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
},
body: JSON.stringify({
"body": "Dag Jan, we komen morgen tussen 9 en 11 uur langs."
}),
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/messages",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
json={
"body": "Dag Jan, we komen morgen tussen 9 en 11 uur langs."
},
)
data = response.json()["data"]Response 201
{
"data": {
"object": "message",
"id": "9d3f9b23-7c8d-4e9f-8012-b3c4d5e6f7a2",
"conversation_id": "9d3f9a12-6b7c-4d8e-9f01-a2b3c4d5e6f1",
"sender": "customer",
"user_id": null,
"body": "Kan de monteur morgen langskomen?",
"email_subject": null,
"attachment": {
"name": "offerte.pdf",
"mime_type": "application/pdf",
"size": 24680
},
"whatsapp_status": null,
"created_at": "2026-09-14T10:15:00Z"
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
422
validation_failed— The input is invalid. -
422
unknown_field— The input contains an unknown field. -
415
unsupported_media_type— This format is not supported. -
413
payload_too_large— The request body is too large. -
404
not_found— Not found. -
409
invalid_state_transition— This action is not possible in the current status. -
409
conflict— This conflicts with the current state. -
403
account_suspended— This company's account has been blocked. -
429
rate_limited— Too many requests. -
422
idempotency_key_reused— This Idempotency-Key was already used for a different request. -
409
idempotency_in_progress— A request with this Idempotency-Key is still in progress.
Close conversation
/api/v1/conversations/{conversation}/close
Closes the conversation, like the button in the inbox. A conversation that is already closed stays as it is.
- Scope
-
messages.send— Send messages to customers and close conversations - Required feature
chat_widget|email|whatsapp
Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.
Path parameters
| Name | Type | Description |
|---|---|---|
conversation required |
string (uuid) | The id (UUID) of the conversation. |
Example request
curl -X POST "https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/close" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f"$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/close', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/close', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
},
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/close",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
)
data = response.json()["data"]Response 200
{
"data": {
"object": "conversation",
"id": "9d3f9a12-6b7c-4d8e-9f01-a2b3c4d5e6f1",
"status": "open",
"channel": "web",
"priority": "normal",
"subject": "Vraag over mijn offerte",
"summary": null,
"page_url": "https://example.com/verandas",
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"name": "Jan de Vries",
"email": "jan@example.com",
"phone": "+31 6 12345678"
},
"assigned_user_id": null,
"quote_id": null,
"invoice_id": null,
"appointment_id": null,
"message_count": 4,
"last_message_at": null,
"closed_at": null,
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
404
not_found— Not found. -
422
idempotency_key_reused— This Idempotency-Key was already used for a different request. -
409
idempotency_in_progress— A request with this Idempotency-Key is still in progress.
The object
All fields are always present; a field without a value is null.
| Field | Type | Description |
|---|---|---|
object |
string | Always "conversation". |
id |
string (uuid) | Id of the conversation. |
status |
string | open, waiting_on_customer, waiting_on_team, snoozed or closed. one of: open, waiting_on_customer, waiting_on_team, snoozed, closed |
channel |
string | Where the conversation runs: web (chat widget), whatsapp or email. one of: web, whatsapp, email |
priority |
string | low, normal, high or urgent. can be empty (null) · one of: low, normal, high, urgent |
subject |
string | The subject; for email the subject line. can be empty (null) |
summary |
string | A short summary of the conversation, if there is one. can be empty (null) |
page_url |
string | The page where the customer started the chat. can be empty (null) |
customer_id |
string (uuid) | The customer this conversation belongs to, if known. can be empty (null) |
customer |
object | The contact details as they belong to this conversation. |
customer.name |
string | Name of the customer. can be empty (null) |
customer.email |
string | Email address of the customer. can be empty (null) |
customer.phone |
string | Phone number of the customer; on WhatsApp the number they write from. can be empty (null) |
assigned_user_id |
string | The team member handling this conversation. can be empty (null) |
quote_id |
string (uuid) | The quote this conversation belongs to, if any. can be empty (null) |
invoice_id |
string (uuid) | The invoice this conversation belongs to, if any. can be empty (null) |
appointment_id |
string (uuid) | The appointment this conversation belongs to, if any. can be empty (null) |
message_count |
integer | The number of messages in this conversation. can be empty (null) |
last_message_at |
string (date-time) | When the last message arrived. can be empty (null) |
closed_at |
string (date-time) | When the conversation was closed. can be empty (null) |
created_at |
string (date-time) | When the conversation started. |
updated_at |
string (date-time) | When the conversation last changed. |