Your AI Connector Docs

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/send and 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 a 404.


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 filter and direction filters are applied to each page after it’s read, so a filtered page can contain fewer items than limit. The next_cursor still advances through the full conversation, so keep paging until next_cursor is null.

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 it id. 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}/messages with is_deleted: true and an empty body and media_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 hours and limit modest 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-flag also 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.