Messages & Conversations
The Messages API lets you send a message to any contact, read back a conversation, correct or remove a message you already sent, react to one, pull a full chat-session thread, export a transcript, and mark chats read or unread — all without opening the inbox.
All paths on this page are relative to the base URL https://api.youraiconnector.com/v1. Every request needs your API key — see Authentication for the full list of ways to send it. The examples below use the X-API-Key header, with one cURL example showing the ?apiKey= query form too.
How delivery works: Sending a message does not wait for it to arrive. The API accepts your message, returns immediately with a message ID, and then delivers it in the background on the contact’s channel (WhatsApp, SMS, Instagram, and so on). To track whether a message was actually delivered or read, listen for status updates with Webhooks — don’t poll. The send response only confirms the message was accepted.
Send a message
There are two ways to send. Pick whichever fits how you already identify the contact:
- Send by contact ID — you already know the contact’s ID (for example, you created the contact through the API or got it from a webhook). Use
POST /contacts/{contactId}/send-message. - Send by contact identity — you know the contact’s phone number, Instagram ID, etc., but not their internal ID. Use
POST /contacts/sendand let the platform find the right contact.
Both queue the message the same way and deliver it on whichever channel the contact is on. You don’t pick a transport — the platform routes WhatsApp contacts over WhatsApp, SMS contacts over SMS, and so on.
Send by contact ID
POST /contacts/{contactId}/send-message
| Field | Required | Description |
|---|---|---|
body |
Yes | The message text to send. |
mediaUrl |
No | URL of a media file (image, document, etc.) to attach. |
mediaContentType |
No | MIME type of the attached media, e.g. image/jpeg. |
pauseBot |
No | true pauses the AI for this contact as the message is sent — for a human taking over. See Pause or resume the AI. |
clearIncompleteReply |
No | true discards a half-finished bot reply so it doesn’t resume after your message. |
cURL
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/send-message" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "Hi! Your appointment is confirmed for tomorrow at 10:00."
}'
JavaScript
const res = await fetch(
"https://api.youraiconnector.com/v1/contacts/contact123/send-message",
{
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
body: "Hi! Your appointment is confirmed for tomorrow at 10:00.",
}),
}
);
const data = await res.json();
console.log(data.messageId);
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/contacts/contact123/send-message",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={"body": "Hi! Your appointment is confirmed for tomorrow at 10:00."},
)
print(res.json()["messageId"])
Response (200 OK):
{
"success": true,
"messageId": "aB3dE5fG7hI9jK1lM2nO",
"contactId": "contact123",
"channel": "whatsapp",
"message": "Message created successfully. Delivery is being processed."
}
Send by contact identity
POST /contacts/send
Use this when you don’t have the contact’s internal ID. Provide the message body plus either a contact_id, or a channel together with the identity field that matches that channel.
| Field | Required | Description |
|---|---|---|
body |
Yes | The message text to send. |
contact_id |
No | ID of an existing contact. When set, the identity fields below are not needed. |
channel |
No | Channel to send on. Required when contact_id is not given. One of the 14 outbound-sendable channels: whatsapp, whatsapp_web, sms, instagram, instagram_private, messenger, telegram, chat-widget, custom, email, line, imessage, linkedin, viber. |
phone_number |
No | Contact’s phone number in international format. Used with whatsapp, whatsapp_web, and sms. |
instagram_id |
No | Contact’s Instagram user ID. Used with instagram. |
messenger_id |
No | Contact’s Messenger user ID. Used with messenger. |
telegram_user_id |
No | Contact’s Telegram user ID. Used with telegram. |
media_url |
No | URL of a media file to attach. |
media_content_type |
No | MIME type of the attached media, e.g. image/jpeg. |
Which channels can be resolved by identity. Only six of the 14 accept an identity field instead of a contact_id: whatsapp, whatsapp_web and sms are looked up by phone_number, instagram by instagram_id, messenger by messenger_id, and telegram by telegram_user_id. The other eight — instagram_private, chat-widget, custom, email, line, imessage, linkedin and viber — have no public identity to look up, so sending on those channels requires contact_id; passing channel alone returns a 400 telling you contact_id is required.
cURL (using the ?apiKey= query form)
curl -X POST "https://api.youraiconnector.com/v1/contacts/send?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"phone_number": "+31612345678",
"body": "Hi! Your appointment is confirmed."
}'
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/contacts/send", {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
channel: "whatsapp",
phone_number: "+31612345678",
body: "Hi! Your appointment is confirmed.",
}),
});
const data = await res.json();
console.log(data.message_id, data.channel);
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/contacts/send",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"channel": "whatsapp",
"phone_number": "+31612345678",
"body": "Hi! Your appointment is confirmed.",
},
)
data = res.json()
print(data["message_id"], data["channel"])
Response (201 Created):
{
"success": true,
"message_id": "aB3dE5fG7hI9jK1lM2nO",
"contact_id": "contact123",
"channel": "whatsapp"
}
Why a message might be rejected: A contact with do-not-disturb or private mode turned on cannot receive outbound messages — the request fails with a
422. If no contact matches the ID or identity you provided, you get a404.
List a contact’s messages
GET /contacts/{contactId}/messages
Returns a contact’s messages, newest first, with cursor-based pagination.
| Query parameter | Required | Description |
|---|---|---|
limit |
No | Page size. Default 50, maximum 100. |
cursor |
No | The next_cursor value from a previous response. Returns messages older than the cursor. |
filter |
No | Filter by content type: all (default), text, media, or tool_use. |
direction |
No | Filter by direction: all (default), inbound (received from the contact), or outbound (sent by you). |
Note on filtering and pagination: The
filteranddirectionfilters are applied to each page after it’s read, so a filtered page can contain fewer items thanlimit. Thenext_cursorstill advances through the full conversation, so keep paging untilnext_cursorisnull.
cURL
curl "https://api.youraiconnector.com/v1/contacts/contact123/messages?limit=50&direction=inbound" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const params = new URLSearchParams({ limit: "50", direction: "inbound" });
const res = await fetch(
`https://api.youraiconnector.com/v1/contacts/contact123/messages?${params}`,
{ headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.messages, data.next_cursor);
Python
import requests
res = requests.get(
"https://api.youraiconnector.com/v1/contacts/contact123/messages",
headers={"X-API-Key": "YOUR_API_KEY"},
params={"limit": 50, "direction": "inbound"},
)
data = res.json()
print(data["messages"], data["next_cursor"])
Response (200 OK):
{
"success": true,
"contact_id": "contact123",
"messages": [
{
"id": "aB3dE5fG7hI9jK1lM2nO",
"body": "Hi! Thanks for reaching out.",
"direction": "inbound",
"channel": "whatsapp",
"status": "delivered",
"type": null,
"timestamp": "2026-06-01T10:00:00.000Z",
"media_url": null,
"media_content_type": null,
"bot_reply": false
}
],
"next_cursor": "cD4eF6gH8iJ0kL2mN3oP"
}
Message fields
| Field | Description |
|---|---|
id |
Unique ID of the message. |
body |
Text content of the message. |
direction |
inbound (received from the contact) or outbound (sent by your account). |
channel |
Channel the message was sent or received on (e.g. whatsapp, sms, instagram). |
status |
Current delivery status, e.g. Created, sent, delivered, read, failed. |
type |
Message type. Plain text messages have a null type; automated assistant tool activity is marked tool_use. |
timestamp |
ISO 8601 time the message was created. |
media_url |
URL of an attached media file, if any. |
media_content_type |
MIME type of the attached media, if any. |
bot_reply |
true when the message was generated by the AI assistant. |
score |
Your rating of the message: 1 thumbs up, -1 thumbs down, 0 when it has not been rated. See Rate or star a message. |
is_important |
true when the message has been starred. |
is_deleted |
true when the message was deleted. Deleted messages stay in the list but their body and media_url are empty. |
reactions |
Emoji reactions on the message, from either side. Always an array — empty when there are none. Each entry has emoji, from_phone_number, from_me (true when the reaction is yours) and reacted_at. |
List chat sessions
A chat session is one conversation window with a contact: it opens when they start talking and closes when the conversation is concluded. Sessions are how you page a long history into readable conversations instead of one endless list.
Recent sessions across all contacts
GET /chat-sessions/recent
Returns the sessions that started in the last X hours, newest first, across every contact on the account.
| Query parameter | Required | Description |
|---|---|---|
hours |
Yes | How many hours to look back. Must be a positive whole number. |
status |
No | Only return sessions with this status: ChatSessionOpened or ChatSessionClosed. |
limit |
No | Maximum number of sessions to return. Default 100, maximum 100. |
includeMessages |
No | true adds a messages array to every session. Off by default because it makes the response much larger. |
cURL
curl "https://api.youraiconnector.com/v1/chat-sessions/recent?hours=24&status=ChatSessionClosed" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const params = new URLSearchParams({ hours: "24", status: "ChatSessionClosed" });
const res = await fetch(`https://api.youraiconnector.com/v1/chat-sessions/recent?${params}`, {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.data.total_sessions, data.data.sessions);
Python
import requests
res = requests.get(
"https://api.youraiconnector.com/v1/chat-sessions/recent",
headers={"X-API-Key": "YOUR_API_KEY"},
params={"hours": 24, "status": "ChatSessionClosed"},
)
data = res.json()["data"]
print(data["total_sessions"], data["sessions"])
Response (200 OK):
{
"success": true,
"data": {
"hours_ago": 24,
"total_sessions": 2,
"sessions": [
{
"session_id": "session456",
"contact_id": "contact123",
"contact_name": "Jane Doe",
"contact_phone": "+31612345678",
"contact_email": "jane@example.com",
"start_date_time": "2026-06-01T09:55:00.000Z",
"end_date_time": "2026-06-01T10:20:00.000Z",
"status": "ChatSessionClosed",
"tag": "Booking enquiry"
}
]
}
}
All sessions for one contact
GET /chat-sessions/{contactId}
Returns every chat session for a single contact. Same status, limit and includeMessages parameters as above — hours does not apply here.
cURL
curl "https://api.youraiconnector.com/v1/chat-sessions/contact123?limit=20" \
-H "X-API-Key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"contact_id": "contact123",
"contact_name": "Jane Doe",
"total_sessions": 2,
"sessions": [
{
"id": "session456",
"start_date_time": "2026-06-01T09:55:00.000Z",
"end_date_time": "2026-06-01T10:20:00.000Z",
"status": "ChatSessionClosed",
"tag": "Booking enquiry"
}
]
}
}
Session ID field names differ between the two endpoints. The recent-sessions list calls it
session_id(it also carries the contact’s details, since sessions come from many contacts); the per-contact list calls itid. Either value is what you pass as{sessionId}when fetching the full thread below.
When includeMessages=true, each session gains a messages array whose entries carry id, body, direction, timestamp, type, channel and status.
Fetch a chat-session thread
GET /contacts/{contactId}/chat-sessions/{sessionId}/messages
A chat session groups a contact’s messages into one conversation window. This endpoint returns the full thread of a single session, oldest first, along with the session’s metadata. You can find session IDs for a contact through the chat sessions endpoints.
cURL
curl "https://api.youraiconnector.com/v1/contacts/contact123/chat-sessions/session456/messages" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch(
"https://api.youraiconnector.com/v1/contacts/contact123/chat-sessions/session456/messages",
{ headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.session, data.messages);
Python
import requests
res = requests.get(
"https://api.youraiconnector.com/v1/contacts/contact123/chat-sessions/session456/messages",
headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["session"], data["messages"])
Response (200 OK):
{
"success": true,
"contact_id": "contact123",
"session": {
"id": "session456",
"status": "ChatSessionClosed",
"start_date_time": "2026-06-01T09:55:00.000Z",
"end_date_time": "2026-06-01T10:20:00.000Z",
"tag": "Booking enquiry"
},
"messages": [
{
"id": "aB3dE5fG7hI9jK1lM2nO",
"body": "Hi! Thanks for reaching out.",
"direction": "inbound",
"channel": "whatsapp",
"status": "delivered",
"type": null,
"timestamp": "2026-06-01T09:55:00.000Z",
"media_url": null,
"media_content_type": null,
"bot_reply": false
}
]
}
The session object reports status (ChatSessionOpened while active, ChatSessionClosed once ended), start_date_time, end_date_time, and a human-readable tag. The messages array uses the same message fields as the list endpoint.
Edit, delete and react to messages
These endpoints change a message after it has been sent. Two of them reach out to the contact’s channel as well as your own copy, so read the section intro before wiring them up — what is possible depends entirely on the channel the conversation is on.
What each channel allows
| Action | Channels that can change the contact’s copy | Time limit |
|---|---|---|
| Edit a sent message | Chat widget, WhatsApp Web, Telegram, LinkedIn | None on the chat widget, 15 minutes on WhatsApp Web, 48 hours on Telegram, 60 minutes on LinkedIn |
| Delete for everyone | Chat widget, WhatsApp Web, Telegram, LinkedIn | 60 minutes on LinkedIn; the others have no published limit |
| React with an emoji | WhatsApp Web, Telegram | None |
On every other channel — the WhatsApp Business API, SMS, Instagram, Messenger, email, LINE, custom channels — a delete still removes the message from your inbox, but the contact keeps their copy, and editing or reacting is not possible at all.
Edit a message
POST /contacts/{contactId}/messages/{messageId}/edit
Rewrites a message you already sent, on the contact’s device and in your copy.
| Field | Required | Description |
|---|---|---|
body |
Yes | The new message text. Must not be empty and can be at most 4096 characters. |
Unlike deleting, this fails loudly when the channel refuses: you get a 409 and your copy is left exactly as the contact has it, because showing an edit they never received would put the two sides out of step. The edit_reason field tells you why — the channel’s edit window has closed, the channel is disconnected, or something else went wrong.
cURL
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/edit" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "body": "Sorry - I meant Thursday at 3pm." }'
JavaScript
const res = await fetch(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/edit",
{
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ body: "Sorry - I meant Thursday at 3pm." }),
}
);
const data = await res.json();
console.log(data.edited, data.edit_reason);
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/edit",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={"body": "Sorry - I meant Thursday at 3pm."},
)
data = res.json()
print(data.get("edited"), data.get("edit_reason"))
Response (200 OK):
{
"success": true,
"contact_id": "contact123",
"message_id": "msg_1",
"edited": true,
"edit_reason": "edit_dispatched"
}
If the channel would not take the edit you get a 409 instead, and nothing was changed:
{
"success": false,
"error": "The message could not be edited",
"edit_reason": "channel_disconnected"
}
A message that has already been deleted, a channel that cannot edit at all, and a message that is too old for its channel all return 400 — the request never reaches the channel.
Delete one message
DELETE /contacts/{contactId}/messages/{messageId}
Removes the message from your conversation and, where the channel allows it, pulls the contact’s copy back too. No request body.
This always answers 200 when the message existed, even if the contact’s copy could not be retracted — your copy is gone, so an error would be misleading. Read the three fields in the response to tell the user what actually happened:
| Field | Description |
|---|---|
revoke_supported |
Whether this channel can retract messages at all. |
revoked |
Whether the copy on the contact’s device was removed. |
revoke_reason |
Why it was not removed, when revoked is false — for example revoke_window_closed or already_deleted. |
cURL
curl -X DELETE "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1",
{ method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.revoked, data.revoke_reason);
Python
import requests
res = requests.delete(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1",
headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["revoked"], data["revoke_reason"])
Response (200 OK):
{
"success": true,
"contact_id": "contact123",
"message_id": "msg_1",
"revoke_supported": true,
"revoked": true,
"revoke_reason": "revoke_dispatched"
}
Deleted messages are not removed from the conversation history. They stay in
GET /contacts/{contactId}/messageswithis_deleted: trueand an emptybodyandmedia_url.
Delete several messages at once
POST /contacts/{contactId}/messages/bulk-delete
Clears a batch of messages from your side only. The bodies and attachments are emptied, but nothing is retracted on the contact’s device — to also pull a message back, delete it one at a time with the single-message endpoint above.
| Field | Required | Description |
|---|---|---|
message_ids |
Yes | A non-empty array of message IDs, up to 500 per request. messageIds is accepted as an alias. |
cURL
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/messages/bulk-delete" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message_ids": ["msg_1", "msg_2"] }'
JavaScript
const res = await fetch(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/bulk-delete",
{
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ message_ids: ["msg_1", "msg_2"] }),
}
);
console.log((await res.json()).deleted);
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/bulk-delete",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={"message_ids": ["msg_1", "msg_2"]},
)
print(res.json()["deleted"])
Response (200 OK):
{
"success": true,
"contact_id": "contact123",
"deleted": 2
}
React to a message
POST /contacts/{contactId}/messages/{messageId}/react
Puts your own emoji reaction on a message, or takes it back by sending an empty string. The contact’s own reactions are never touched.
| Field | Required | Description |
|---|---|---|
emoji |
Yes | The emoji to react with, or "" to remove your reaction. Must be a single string with no spaces, at most 16 characters. |
Like editing, this fails rather than showing a reaction the contact never got, and the failure tells you whether retrying is worth it:
422— it can never be delivered on this conversation: the channel does not support reactions, the message has no channel-side ID, or the emoji is outside the set that channel allows.409— the channel was momentarily unreachable. A retry may work.
cURL
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/react" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "emoji": "👍" }'
JavaScript
const res = await fetch(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/react",
{
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ emoji: "👍" }),
}
);
const data = await res.json();
console.log(data.reactions);
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/react",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={"emoji": "👍"},
)
print(res.json()["reactions"])
Response (200 OK):
{
"success": true,
"contact_id": "contact123",
"message_id": "msg_1",
"reaction_supported": true,
"reaction_reason": "reaction_dispatched",
"reactions": [
{
"emoji": "👍",
"from_phone_number": "+31612345678",
"from_me": true,
"reacted_at": "2026-06-01T10:05:00.000Z"
}
]
}
The reactions array is the full set of reactions now on the message, yours and the contact’s. On a 409 or 422 it is returned unchanged, so a client rendering straight from it never shows a reaction that was not delivered.
Rate or star a message
PATCH /contacts/{contactId}/messages/{messageId}
Rates a message thumbs up or thumbs down and/or stars it as important. This is bookkeeping on your side only — nothing is sent to the contact.
| Field | Required | Description |
|---|---|---|
score |
No | 1 thumbs up, -1 thumbs down, 0 clears the rating. |
is_important |
No | true stars the message, false unstars it. Must be a real boolean, not the string "true". |
Send at least one of the two, or you get a 400. Only what you send is written, so starring a message never clears its rating and vice versa — and the response echoes back only the fields you sent.
cURL
curl -X PATCH "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "score": 1, "is_important": true }'
JavaScript
await fetch("https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1", {
method: "PATCH",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ score: 1, is_important: true }),
});
Python
import requests
requests.patch(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"score": 1, "is_important": True},
)
Response (200 OK):
{
"success": true,
"contact_id": "contact123",
"message_id": "msg_1",
"score": 1,
"is_important": true
}
Mark messages as read
You can clear the unread state either for specific messages or for the whole conversation.
Mark specific messages as read
POST /contacts/{contactId}/messages/mark-read
Pass the IDs of the messages to mark as read.
| Field | Required | Description |
|---|---|---|
message_ids |
Yes | A non-empty array of message IDs (up to 500 per request). |
cURL
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/messages/mark-read" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message_ids": ["aB3dE5fG7hI9jK1lM2nO", "cD4eF6gH8iJ0kL2mN3oP"]
}'
JavaScript
const res = await fetch(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/mark-read",
{
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
message_ids: ["aB3dE5fG7hI9jK1lM2nO", "cD4eF6gH8iJ0kL2mN3oP"],
}),
}
);
const data = await res.json();
console.log(data.marked_read);
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/contacts/contact123/messages/mark-read",
headers={
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={"message_ids": ["aB3dE5fG7hI9jK1lM2nO", "cD4eF6gH8iJ0kL2mN3oP"]},
)
print(res.json()["marked_read"])
Response (200 OK):
{
"success": true,
"contact_id": "contact123",
"marked_read": 2
}
Mark the whole chat as read
POST /contacts/{contactId}/mark-read
Clears the unread badge for the contact’s entire conversation in the inbox. No request body is required.
cURL
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/mark-read" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch(
"https://api.youraiconnector.com/v1/contacts/contact123/mark-read",
{ method: "POST", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.success);
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/contacts/contact123/mark-read",
headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
Response (200 OK):
{
"success": true,
"contact_id": "contact123"
}
Mark the whole chat as unread
POST /contacts/{contactId}/mark-unread
Puts the unread badge back on the conversation — handy when someone on your team opened a chat but is handing it back. No request body is required.
This is an inbox-only flag: it does not change when the conversation was last read, so no read receipt is sent to the contact on channels that support them.
cURL
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/mark-unread" \
-H "X-API-Key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"contact_id": "contact123"
}
Export a conversation
Exports give you a whole conversation as a readable transcript, rather than paging through messages. Every export endpoint accepts a filter of all (default), text, media or tool_use, matching the filter on the message list.
Export one contact’s chat
GET /chat-exports/{contactId}
| Query parameter | Required | Description |
|---|---|---|
format |
No | txt (default) returns a download link to a plain-text transcript. json returns the messages as structured data in the response. |
filter |
No | all (default), text, media or tool_use. |
cURL
curl "https://api.youraiconnector.com/v1/chat-exports/contact123?format=json" \
-H "X-API-Key: YOUR_API_KEY"
Python
import requests
res = requests.get(
"https://api.youraiconnector.com/v1/chat-exports/contact123",
headers={"X-API-Key": "YOUR_API_KEY"},
params={"format": "json"},
)
print(res.json()["data"]["messages"])
Response with format=json (200 OK):
{
"success": true,
"data": {
"contact": {
"id": "contact123",
"name": "Jane Doe",
"phone": "+31612345678",
"email": "jane@example.com"
},
"messages": [
{
"body": "Hi! I have a question about my order.",
"direction": "inbound",
"timestamp": "2026-06-01T09:55:00.000Z",
"type": "text",
"media_url": null,
"media_content_type": null,
"name": null,
"args": null
}
]
}
}
With format=txt (the default), data is instead a download link to the generated transcript file:
{
"success": true,
"data": "https://storage.googleapis.com/.../chat-export-contact123-....txt"
}
The download link is short-lived. Fetch the file as soon as you get the link rather than storing it — request a fresh export when you need the transcript again.
Export every recent conversation
GET /chat-exports/recent
Exports the conversations of all contacts that were active in the last X hours, in one call.
| Query parameter | Required | Description |
|---|---|---|
hours |
Yes | How many hours of activity to look back over. Must be a positive whole number. |
format |
No | json (default) returns one entry per contact. txt returns a single downloadable text file with every conversation in it. |
limit |
No | Maximum number of contacts to export. Default 50, maximum 100. |
filter |
No | all (default), text, media or tool_use. |
cURL
curl "https://api.youraiconnector.com/v1/chat-exports/recent?hours=24&limit=25" \
-H "X-API-Key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"hours_ago": 24,
"total_contacts": 2,
"exports": [
{
"contactId": "contact123",
"contactName": "Jane Doe",
"phoneNumber": "+31612345678",
"email": "jane@example.com",
"messageCount": 12,
"chatExport": "Acme Export - Jane Doe\nPhone: +31612345678\n..."
}
]
}
}
With format=txt the response is the text file itself, sent as a download rather than JSON.
This one call pulls the full history of every matching contact, so keep
hoursandlimitmodest on busy accounts.
Email a transcript to the contact
POST /chat-exports/{contactId}/email
Sends the contact their own conversation transcript by email — the “email me this chat” flow, driven from your own system.
| Field | Required | Description |
|---|---|---|
recipient_email |
No | Where to send it. Defaults to the contact’s stored email address. |
via |
No | auto (default) picks the best route, transactional sends it as a system email, email_channel sends it from your connected email channel. |
note |
No | A short line from you shown above the transcript. Up to 1000 characters. |
cURL
curl -X POST "https://api.youraiconnector.com/v1/chat-exports/contact123/email" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "note": "Here is a copy of our chat, as promised." }'
Response (200 OK):
{
"success": true,
"data": {
"via": "transactional",
"recipientEmail": "jane@example.com",
"messageCount": 42,
"omittedCount": 0
}
}
omittedCount tells you how many of the oldest messages were left out to keep the email a sensible length. A 200 means the transcript was built and queued for sending, not that it has landed in the inbox yet.
Pause or resume the AI for one contact
PUT /contacts/{contactId}
Set is_bot_active to false to stop the AI replying to one contact, and back to true to hand the conversation back. This is the takeover switch you want when a human steps into a conversation: outgoing messages you send with the API still deliver while the bot is paused.
cURL
curl -X PUT "https://api.youraiconnector.com/v1/contacts/contact123" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_bot_active": false }'
JavaScript
await fetch("https://api.youraiconnector.com/v1/contacts/contact123", {
method: "PUT",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ is_bot_active: false }),
});
Python
import requests
requests.put(
"https://api.youraiconnector.com/v1/contacts/contact123",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"is_bot_active": False},
)
Response
{
"success": true,
"contact_id": "contact123"
}
Pausing as part of the reply
If a human is taking over by sending a reply, you can pause the bot in the same request instead of making a second call. POST /contacts/{contactId}/send-message accepts two optional flags:
| Field | Description |
|---|---|
pauseBot |
true pauses the AI for this contact as the message is sent. |
clearIncompleteReply |
true discards a half-finished bot reply so it does not resume afterwards. |
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/send-message" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "Hi, Sarah here - taking over from the assistant.",
"pauseBot": true,
"clearIncompleteReply": true
}'
The response includes "botPaused": true when the pause was applied.
Flagging a contact as private with
POST /contacts/bulk-flagalso pauses the bot for them. See Contacts for the full field list.
Building your own inbox
Everything an inbox needs is on this page and in Contacts:
| What you need | Endpoint |
|---|---|
| List conversations | GET /contacts |
| Read a conversation | GET /contacts/{contactId}/messages |
| List a contact’s chat sessions | GET /chat-sessions/{contactId} |
| See what came in recently | GET /chat-sessions/recent |
| Read one chat session | GET /contacts/{contactId}/chat-sessions/{sessionId}/messages |
| Send a manual reply | POST /contacts/{contactId}/send-message |
| Correct a reply you just sent | POST /contacts/{contactId}/messages/{messageId}/edit |
| Remove a message | DELETE /contacts/{contactId}/messages/{messageId} |
| Clear several messages | POST /contacts/{contactId}/messages/bulk-delete |
| React with an emoji | POST /contacts/{contactId}/messages/{messageId}/react |
| Rate or star a message | PATCH /contacts/{contactId}/messages/{messageId} |
| Mark as read | POST /contacts/{contactId}/mark-read |
| Hand a chat back to the team | POST /contacts/{contactId}/mark-unread |
| Export a transcript | GET /chat-exports/{contactId} |
| Pause or resume the AI | PUT /contacts/{contactId} with is_bot_active |
For live updates, subscribe to the New Message, Replies, Human Alerted and Chat Concluded events with Webhooks rather than polling this API on a timer.
Messages API errors
Message endpoints return the standard error envelope:
{
"success": false,
"error": "Contact not found"
}
| Status | When it happens on a message endpoint |
|---|---|
400 |
A required field is missing or a parameter is invalid (bad limit, hours, filter, direction, status, an empty or over-500 message_ids array, an invalid cursor, an empty or over-long edit body, a score outside -1/0/1, or an emoji with spaces or over 16 characters). Also returned when a message cannot be edited at all — it was deleted, its channel has no edit, or it is past that channel’s edit window. |
404 |
The contact, chat session, or one of the supplied message IDs was not found. |
409 |
The channel would not take the change right now. Nothing was written: on an edit, edit_reason says why; on a reaction, the channel was momentarily unreachable and a retry may work. |
422 |
The contact cannot receive outbound messages (do-not-disturb, private, or an unsupported channel), or a reaction can never be delivered on this conversation (reaction_reason says which). |
The shared codes every endpoint can return — 401, 403 (your plan does not include API access), 429 (rate limit) and 500 — are listed with retry guidance in Errors & Pagination.
Next steps
- Webhooks — get delivery-status updates pushed to you instead of polling.
- Contacts — create and look up the contacts you message.
- Appointments — book and manage appointments for your contacts.