- Docs
- Developers
REST API
Read and change your TalkingDot workspace from your own servers: sync contacts, record events, start and answer conversations, and manage help-center articles.
Overview
The REST API is organised around resources (contacts, conversations, articles and so on), uses standard HTTP methods and status codes, and accepts and returns JSON. Every request is scoped to the workspace that owns the API key.
| Base URL | https://talkingdot.com/api/v1 |
|---|---|
| Authentication | Authorization: Bearer <api key> |
| Format | JSON request bodies and JSON responses |
| IDs | Integers |
| Timestamps | ISO 8601 in UTC, for example 2026-10-03T09:15:00Z |
| Rate limit | 120 requests per minute per API key |
Try it with your key:
curl https://talkingdot.com/api/v1/me \
-H "Authorization: Bearer cn_live_xxxxxxxx"Authentication
Authenticate every request with an API key in the Authorization header, as a bearer token. Workspace admins create keys in Settings → API. Keys look like cn_live_xxxxxxxx and belong to one workspace.
curl https://talkingdot.com/api/v1/contacts \
-H "Authorization: Bearer cn_live_xxxxxxxx"A request with a missing, invalid or revoked key gets a 401 response with the error code unauthorized.
Keep API keys secret. A key can read and change your whole workspace, including contacts' personal data. Use it only from your servers: never in browser JavaScript, mobile apps or public repositories. Copy a new key when it's created, because only a hash of it is stored. If a key leaks, revoke it in Settings → API and create a new one.
Requests and responses
Send request bodies as JSON with a Content-Type: application/json header. Every response is JSON.
A successful response has a 2xx status (for example 201 Created for a new contact) and wraps the result in data. For a single object, data is that object:
{
"data": {
"id": 1842,
"name": "Ada Lovelace",
"email": "ada@example.com"
}
}For lists, data is an array and meta holds the pagination cursor:
{
"data": [
{ "id": 1842, "name": "Ada Lovelace", "email": "ada@example.com" }
],
"meta": {
"next_cursor": "eyJpZCI6MTg0Mn0"
}
}These two snippets are shortened; real objects contain every field listed in the object reference. Fields without a value are null, never left out.
Pagination
List endpoints return results a page at a time. Control the page with two query parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | How many results to return, from 1 to 100. Default 25. |
cursor | string | The meta.next_cursor value from the previous page. Leave it out for the first page. |
Keep requesting with the latest next_cursor until it comes back as null. Treat cursors as opaque strings: don't build or change them yourself.
curl "https://talkingdot.com/api/v1/conversations?status=open&limit=100" \
-H "Authorization: Bearer cn_live_xxxxxxxx"
curl "https://talkingdot.com/api/v1/conversations?status=open&limit=100&cursor=eyJpZCI6NTUzMX0" \
-H "Authorization: Bearer cn_live_xxxxxxxx"The same loop in Node.js 18+:
async function listAllOpenConversations() {
const results = [];
let cursor = null;
do {
const url = new URL('https://talkingdot.com/api/v1/conversations');
url.searchParams.set('status', 'open');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.TALKINGDOT_API_KEY}` },
});
if (!res.ok) throw new Error(`API request failed with HTTP ${res.status}`);
const body = await res.json();
results.push(...body.data);
cursor = body.meta.next_cursor;
} while (cursor);
return results;
}Errors
Failed requests return a 4xx status and an error object. Branch on code; message is a human-readable explanation that may change.
{
"error": {
"code": "not_found",
"message": "Contact not found."
}
}| HTTP status | code | Meaning |
|---|---|---|
401 | unauthorized | The API key is missing, invalid or revoked. |
403 | forbidden | The key is valid but isn't allowed to make this request. |
404 | not_found | The object doesn't exist in this workspace, or the endpoint doesn't exist. |
422 | validation | The request body or a query parameter is invalid, for example a malformed email or a user ID another contact already has. message says what to fix. |
429 | rate_limited | Too many requests. Wait for the number of seconds in the Retry-After header, then try again. |
Rate limits
Each API key can make 120 requests per minute. Every response tells you where you stand:
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per minute for this key (120). |
X-RateLimit-Remaining | Requests left in the current minute. |
Retry-After | Sent with a 429 response: seconds to wait before retrying. |
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 17
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
{"error": {"code": "rate_limited", "message": "Too many requests."}}For bulk jobs such as a contact import, keep an eye on X-RateLimit-Remaining and pause when it reaches 0, rather than waiting for a 429.
Me
/api/v1/meReturns details of the workspace your API key belongs to. Use it to check that a key works before you use it for anything else.
curl https://talkingdot.com/api/v1/me \
-H "Authorization: Bearer cn_live_xxxxxxxx"Contacts
A contact is anyone who has used your messenger or whom you've added: an anonymous visitor, a lead who has given contact details such as an email, or a user identified with your own user ID (external_id). See the contact object.
List contacts
/api/v1/contactsReturns a page of contacts. Combine the filters to narrow the list.
| Query parameter | Type | Description |
|---|---|---|
email | string | Only contacts with this email address. |
external_id | string | Only the contact with this user ID (the user_id you pass to the messenger). |
type | string | visitor, lead or user. |
limit, cursor | See Pagination. |
curl "https://talkingdot.com/api/v1/contacts?email=ada@example.com" \
-H "Authorization: Bearer cn_live_xxxxxxxx"{
"data": [
{
"id": 1842,
"type": "user",
"external_id": "42",
"name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+44 20 7946 0958",
"company": "Analytical Engines Ltd",
"avatar_url": null,
"verified": true,
"location": {
"country": "GB",
"region": "England",
"city": "London",
"timezone": "Europe/London"
},
"language": "en",
"browser": "Chrome",
"os": "macOS",
"device": "desktop",
"custom_attributes": {
"plan": "pro",
"seats": 5
},
"tags": [
{ "id": 3, "name": "vip" }
],
"sessions": 12,
"page_views": 87,
"last_page_url": "https://www.example.com/pricing",
"unsubscribed": false,
"blocked": false,
"first_seen_at": "2026-09-14T08:02:11Z",
"last_seen_at": "2026-10-03T09:15:00Z",
"created_at": "2026-09-14T08:02:11Z",
"updated_at": "2026-10-03T09:15:00Z"
}
],
"meta": {
"next_cursor": null
}
}Create a contact
/api/v1/contactsAdds a contact, for example when someone signs up in your product. Returns 201 Created and the new contact. A contact with an external_id is a user; one with only contact details is a lead.
| Body field | Type | Description |
|---|---|---|
external_id | string | Your own ID for this user: the same value you pass as user_id to the messenger. Must be unique in the workspace. |
email | string | Email address. Must be valid. |
name | string | Full name. |
phone | string | Phone number. |
company | string | Company or organisation name. |
custom_attributes | object | Key/value pairs with string, number or boolean values, for example {"plan": "pro"}. |
curl https://talkingdot.com/api/v1/contacts \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"external_id": "43",
"email": "charles@example.com",
"name": "Charles Babbage",
"company": "Analytical Engines Ltd",
"custom_attributes": { "plan": "pro", "seats": 5 }
}'{
"data": {
"id": 1907,
"type": "user",
"external_id": "43",
"name": "Charles Babbage",
"email": "charles@example.com",
"phone": null,
"company": "Analytical Engines Ltd",
"avatar_url": null,
"verified": false,
"location": {
"country": null,
"region": null,
"city": null,
"timezone": null
},
"language": null,
"browser": null,
"os": null,
"device": null,
"custom_attributes": {
"plan": "pro",
"seats": 5
},
"tags": [],
"sessions": 0,
"page_views": 0,
"last_page_url": null,
"unsubscribed": false,
"blocked": false,
"first_seen_at": "2026-10-03T09:20:00Z",
"last_seen_at": "2026-10-03T09:20:00Z",
"created_at": "2026-10-03T09:20:00Z",
"updated_at": "2026-10-03T09:20:00Z"
}
}Retrieve a contact
/api/v1/contacts/{id}Returns one contact.
curl https://talkingdot.com/api/v1/contacts/1842 \
-H "Authorization: Bearer cn_live_xxxxxxxx"Update a contact
/api/v1/contacts/{id}Changes a contact. Takes the same fields as Create a contact; only the fields you send are changed. custom_attributes are merged into the existing ones: keys you send are added or overwritten, and a key set to null is removed. Returns the updated contact.
curl -X PATCH https://talkingdot.com/api/v1/contacts/1842 \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"company": "Difference Engines plc",
"custom_attributes": { "plan": "enterprise", "seats": 25 }
}'Delete a contact
/api/v1/contacts/{id}Permanently erases the contact and everything stored about them, including all of their conversations and messages, events, notes and page views. Use it for GDPR erasure requests. This can't be undone. A contact.deleted webhook is sent.
curl -X DELETE https://talkingdot.com/api/v1/contacts/1842 \
-H "Authorization: Bearer cn_live_xxxxxxxx"Contact events
/api/v1/contacts/{id}/eventsRecords something the contact did, such as paying an invoice or finishing onboarding. Events appear in the contact sidebar in the Inbox. This is the server-side version of TalkingDot('track', …) in the JavaScript API.
| Body field | Type | Description |
|---|---|---|
name | string | Required. The event name. Letters, numbers, spaces and . _ - :, up to 80 characters. |
meta | object | Optional details: up to 20 flat values (strings, numbers or booleans). |
curl https://talkingdot.com/api/v1/contacts/1842/events \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "invoice_paid",
"meta": { "invoice": "INV-1042", "amount": 49, "currency": "EUR" }
}'Conversations
A conversation is a thread between a contact and your team. See the conversation object.
List conversations
/api/v1/conversationsReturns a page of conversations, without their messages.
| Query parameter | Type | Description |
|---|---|---|
status | string | open, snoozed or closed. |
assignee_id | integer | Only conversations assigned to this teammate. |
contact_id | integer | Only this contact's conversations. |
limit, cursor | See Pagination. |
curl "https://talkingdot.com/api/v1/conversations?status=open&assignee_id=7" \
-H "Authorization: Bearer cn_live_xxxxxxxx"{
"data": [
{
"id": 5531,
"number": 318,
"contact_id": 1842,
"status": "open",
"priority": false,
"assignee_id": 7,
"team_id": 2,
"channel": "chat",
"started_by": "contact",
"subject": null,
"source_url": "https://www.example.com/pricing",
"tags": [
{ "id": 5, "name": "billing" }
],
"rating": null,
"first_response_seconds": 94,
"snoozed_until": null,
"waiting_since": "2026-10-03T09:15:00Z",
"last_message_at": "2026-10-03T09:15:00Z",
"created_at": "2026-10-03T09:01:30Z",
"closed_at": null,
"url": "https://talkingdot.com/app/12/inbox/5531"
}
],
"meta": {
"next_cursor": null
}
}Retrieve a conversation
/api/v1/conversations/{id}Returns one conversation with its messages. Check each message's type: message for replies, note for internal notes only your team sees, and event for timeline entries.
curl https://talkingdot.com/api/v1/conversations/5531 \
-H "Authorization: Bearer cn_live_xxxxxxxx"{
"data": {
"id": 5531,
"number": 318,
"contact_id": 1842,
"status": "open",
"priority": false,
"assignee_id": 7,
"team_id": 2,
"channel": "chat",
"started_by": "contact",
"subject": null,
"source_url": "https://www.example.com/pricing",
"tags": [
{ "id": 5, "name": "billing" }
],
"rating": null,
"first_response_seconds": 94,
"snoozed_until": null,
"waiting_since": "2026-10-03T09:15:00Z",
"last_message_at": "2026-10-03T09:15:00Z",
"created_at": "2026-10-03T09:01:30Z",
"closed_at": null,
"url": "https://talkingdot.com/app/12/inbox/5531",
"messages": [
{
"id": 88190,
"conversation_id": 5531,
"type": "message",
"author_type": "contact",
"author": { "id": 1842 },
"body": "Hi! Can I switch my plan to annual billing?",
"attachments": [],
"via": "widget",
"created_at": "2026-10-03T09:01:30Z"
},
{
"id": 88197,
"conversation_id": 5531,
"type": "message",
"author_type": "agent",
"author": { "id": 7, "name": "Grace Hopper", "email": "grace@example.com" },
"body": "Hi Ada, yes you can. Here's the invoice for the annual plan.",
"attachments": [
{
"name": "invoice-1042.pdf",
"url": "https://talkingdot.com/files/901/3f9c2a7b1e/invoice-1042.pdf",
"size": 48213,
"content_type": "application/pdf"
}
],
"via": "web",
"created_at": "2026-10-03T09:03:04Z"
},
{
"id": 88213,
"conversation_id": 5531,
"type": "message",
"author_type": "contact",
"author": { "id": 1842 },
"body": "Thanks! I've just paid it.",
"attachments": [],
"via": "widget",
"created_at": "2026-10-03T09:15:00Z"
}
]
}
}Start a conversation
/api/v1/conversationsStarts an outbound conversation: a new conversation with a contact, opened by a message from your team. Returns the new conversation.
| Body field | Type | Description |
|---|---|---|
contact_id | integer | Required. The contact to write to. |
body | string | Required. The first message. |
teammate_id | integer | Optional. The teammate the message is sent as. |
curl https://talkingdot.com/api/v1/conversations \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 1842,
"body": "Hi Ada, your annual plan is active. Anything else we can help with?",
"teammate_id": 7
}'Update a conversation
/api/v1/conversations/{id}Changes a conversation's status, assignment, priority or tags. Send only the fields you want to change. Returns the updated conversation.
| Body field | Type | Description |
|---|---|---|
status | string | open, snoozed or closed. |
assignee_id | integer or null | The teammate to assign, or null to unassign. |
team_id | integer or null | The team to assign, or null to remove the team. |
priority | boolean | true marks the conversation as priority, false clears it. |
snoozed_until | string | When a snoozed conversation should reopen, as an ISO 8601 timestamp. Use it with "status": "snoozed". |
tags | array of strings | The conversation's tags, by name, for example ["billing", "annual-plan"]. |
# Assign to a teammate and team, mark as priority and tag it
curl -X PATCH https://talkingdot.com/api/v1/conversations/5531 \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"assignee_id": 7,
"team_id": 2,
"priority": true,
"tags": ["billing", "annual-plan"]
}'
# Snooze until Monday morning
curl -X PATCH https://talkingdot.com/api/v1/conversations/5531 \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "status": "snoozed", "snoozed_until": "2026-10-05T08:00:00Z" }'
# Close it
curl -X PATCH https://talkingdot.com/api/v1/conversations/5531 \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "status": "closed" }'Replies and notes
Add messages to an existing conversation, either from your team or on behalf of the contact. Each message is a message object.
Reply as a teammate
/api/v1/conversations/{id}/replySends a reply the contact sees, or adds an internal note only your team sees. Returns the new message.
| Body field | Type | Description |
|---|---|---|
body | string | Required. The text of the reply or note. |
type | string | Required. comment for a reply to the contact, or note for an internal note. |
teammate_id | integer | Optional. The teammate the reply or note is written as. |
curl https://talkingdot.com/api/v1/conversations/5531/reply \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"type": "comment",
"body": "Payment received, thank you! Your plan is now billed annually.",
"teammate_id": 7
}'{
"data": {
"id": 88220,
"conversation_id": 5531,
"type": "message",
"author_type": "agent",
"author": { "id": 7, "name": "Grace Hopper", "email": "grace@example.com" },
"body": "Payment received, thank you! Your plan is now billed annually.",
"attachments": [],
"via": "api",
"created_at": "2026-10-03T09:18:42Z"
}
}To leave a note for your team instead, send "type": "note". The contact never sees notes.
Reply as the contact
/api/v1/conversations/{id}/contact-replyAdds a message from the conversation's contact, as if they had written it themselves. Use it to bring in messages the contact sent through your own app or another channel.
| Body field | Type | Description |
|---|---|---|
body | string | Required. The contact's message. |
curl https://talkingdot.com/api/v1/conversations/5531/contact-reply \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "body": "Great, thanks for the quick help!" }'Teammates and teams
List teammates
/api/v1/teammatesReturns the people in your workspace. Use a teammate's id as assignee_id or teammate_id. role is owner, admin or agent; availability is online or away.
curl https://talkingdot.com/api/v1/teammates \
-H "Authorization: Bearer cn_live_xxxxxxxx"{
"data": [
{
"id": 7,
"name": "Grace Hopper",
"email": "grace@example.com",
"role": "admin",
"availability": "online",
"avatar_url": null
}
],
"meta": {
"next_cursor": null
}
}List teams
/api/v1/teamsReturns your workspace's teams. Use a team's id as team_id when you update a conversation.
curl https://talkingdot.com/api/v1/teams \
-H "Authorization: Bearer cn_live_xxxxxxxx"Tags
/api/v1/tagsReturns your workspace's tags, each with an id and a name. Tag conversations by name with PATCH /api/v1/conversations/{id}.
curl https://talkingdot.com/api/v1/tags \
-H "Authorization: Bearer cn_live_xxxxxxxx"Help center
Manage the articles in your help center. Articles are grouped into collections and are either draft or published.
List collections
/api/v1/collectionsReturns your help-center collections. Use a collection's id as an article's collection_id.
curl https://talkingdot.com/api/v1/collections \
-H "Authorization: Bearer cn_live_xxxxxxxx"List articles
/api/v1/articlesReturns a page of articles.
| Query parameter | Type | Description |
|---|---|---|
status | string | draft or published. |
limit, cursor | See Pagination. |
curl "https://talkingdot.com/api/v1/articles?status=published" \
-H "Authorization: Bearer cn_live_xxxxxxxx"Retrieve an article
/api/v1/articles/{id}Returns one article, including its HTML body and its view and "was this helpful?" counts.
curl https://talkingdot.com/api/v1/articles/64 \
-H "Authorization: Bearer cn_live_xxxxxxxx"{
"data": {
"id": 64,
"title": "How refunds work",
"slug": "how-refunds-work",
"excerpt": "Where to request a refund and what happens next.",
"body_html": "<p>Open <strong>Billing</strong> and choose <strong>Request refund</strong>.</p>",
"status": "published",
"collection_id": 3,
"views": 1204,
"helpful": 87,
"unhelpful": 6,
"url": "https://talkingdot.com/help/acme/articles/64-how-refunds-work",
"created_at": "2026-08-21T14:00:00Z",
"updated_at": "2026-09-30T10:12:45Z"
}
}Create an article
/api/v1/articlesAdds an article. Returns the new article.
| Body field | Type | Description |
|---|---|---|
title | string | Required. The article title. |
body_html | string | The article body as HTML. |
collection_id | integer | The collection to put it in. |
status | string | draft or published. |
curl https://talkingdot.com/api/v1/articles \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"title": "Switching to annual billing",
"body_html": "<p>Open <strong>Billing</strong>, choose <strong>Change plan</strong> and pick <strong>Annual</strong>.</p>",
"collection_id": 3,
"status": "draft"
}'Update an article
/api/v1/articles/{id}Changes an article. Takes the same fields as Create an article; send only the ones you want to change. Returns the updated article.
curl -X PATCH https://talkingdot.com/api/v1/articles/64 \
-H "Authorization: Bearer cn_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "status": "published" }'Delete an article
/api/v1/articles/{id}Deletes an article. It disappears from your help center and from search in the messenger.
curl -X DELETE https://talkingdot.com/api/v1/articles/64 \
-H "Authorization: Bearer cn_live_xxxxxxxx"Object reference
The same objects are used in API responses and in webhook payloads. Timestamps are ISO 8601 in UTC; fields without a value are null.
Contact
| Field | Type | Description |
|---|---|---|
id | integer | Unique ID of the contact. |
type | string | visitor (anonymous), lead (has given contact details, such as an email) or user (has an external_id). |
external_id | string or null | Your own user ID: the user_id passed to the messenger, or set through the API. |
name | string or null | Full name. |
email | string or null | Email address, in lowercase. |
phone | string or null | Phone number. |
company | string or null | Company or organisation. |
avatar_url | string or null | URL of the contact's picture. |
verified | boolean | true when the identity was proven with a valid user_hash. See Identity verification. |
location | object | country (two-letter code, such as GB), region, city and timezone (such as Europe/London). Each can be null. |
language | string or null | Browser language code, such as en. |
browser | string or null | Browser name, such as Chrome. |
os | string or null | Operating system, such as macOS. |
device | string or null | desktop, tablet or mobile. |
custom_attributes | object | Your own key/value data. An empty object when there is none. |
tags | array | Tags on the contact, each {"id", "name"}. |
sessions | integer | Number of visits. |
page_views | integer | Number of pages viewed. |
last_page_url | string or null | The last page the contact viewed. |
unsubscribed | boolean | true when the contact has unsubscribed from emails. |
blocked | boolean | true when your team has blocked the contact. |
first_seen_at | timestamp | When the contact was first seen. |
last_seen_at | timestamp | When the contact was last seen. |
created_at | timestamp | When the contact was created. |
updated_at | timestamp or null | When the contact's details last changed. |
Conversation
| Field | Type | Description |
|---|---|---|
id | integer | Unique ID of the conversation. |
number | integer | The conversation's number within your workspace. |
contact_id | integer | The contact the conversation is with. |
status | string | open, snoozed or closed. |
priority | boolean | true when marked as priority. |
assignee_id | integer or null | The assigned teammate. |
team_id | integer or null | The assigned team. |
channel | string | chat, email or api. |
started_by | string | Who started the conversation: contact, agent, auto (an auto message) or api. |
subject | string or null | Subject line, mainly for email conversations. |
source_url | string or null | The page the conversation started on. |
tags | array | Tags on the conversation, each {"id", "name"}. |
rating | object or null | The contact's rating: score (1 to 5), comment and rated_at. null until they rate. |
first_response_seconds | integer or null | Seconds from the start of the conversation to your team's first reply. |
snoozed_until | timestamp or null | When a snoozed conversation reopens. |
waiting_since | timestamp or null | When the contact started waiting for a reply; null when your team has replied. |
last_message_at | timestamp or null | Time of the latest message. |
created_at | timestamp | When the conversation started. |
closed_at | timestamp or null | When it was closed. |
url | string | Link to the conversation in the Inbox (for teammates). |
Message
| Field | Type | Description |
|---|---|---|
id | integer | Unique ID of the message. |
conversation_id | integer | The conversation it belongs to. |
type | string | message (seen by the contact), note (internal) or event (a timeline entry, such as an assignment). |
author_type | string | contact, agent (a teammate), bot or system. |
author | object or null | For a teammate: id, name and email. For the contact: id. null for bot and system messages. |
body | string | The message text. |
attachments | array | Files, each with name, url, size (bytes) and content_type. |
via | string | Where it was written: widget (the messenger), web (the Inbox), email, api, bot or auto. |
created_at | timestamp | When it was sent. |