Klantly Developers

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

List conversations

GET /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

NameTypeDescription
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
curl "https://app.klantly.com/api/v1/conversations?filter[status]=open&sort=-last_message_at" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"
PHP
$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'];
JavaScript
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();
Python
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.

Example response
{
  "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

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Get conversation

GET /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

NameTypeDescription
conversation required string (uuid) The id (UUID) of the conversation.

Example request

cURL
curl "https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"
PHP
$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'];
JavaScript
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();
Python
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

Example response
{
  "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

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

List messages

GET /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

NameTypeDescription
conversation required string (uuid) The id (UUID) of the conversation.

Query parameters

NameTypeDescription
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
curl "https://app.klantly.com/api/v1/conversations/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/messages" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"
PHP
$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'];
JavaScript
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();
Python
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.

Example response
{
  "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

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Send message

POST /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

NameTypeDescription
conversation required string (uuid) The id (UUID) of the conversation.

Body (JSON)

FieldTypeDescription
body required string The text of the message. at most 10000 characters

Example request

cURL
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."
}'
PHP
$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'];
JavaScript
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();
Python
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

Example response
{
  "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

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Close conversation

POST /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

NameTypeDescription
conversation required string (uuid) The id (UUID) of the conversation.

Example request

cURL
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"
PHP
$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'];
JavaScript
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();
Python
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

Example response
{
  "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

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

The object

All fields are always present; a field without a value is null.

FieldTypeDescription
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.