Inbox

Messages

List conversations, read their messages, reply with text, media, buttons and quick replies, start new DMs, and hand a thread to another app.

Direct messages live in conversations. Each conversation belongs to one connected account and holds the messages between that account and one person (or a group). This page covers reading and answering them. For the overall architecture, start with the Inbox overview.

#List conversations

curl "https://api.pozy.ai/v1/inbox/conversations?profileId=6650c1e2a3b4c5d6e7f80911&status=active&limit=50" \
  -H "Authorization: Bearer $POZY_API_KEY"
Parameter What it does
profileId, accountId, platform Narrow to a customer, one account or one platform
status active or archived
folder inbox (default) or requests for message requests, where the platform separates them
tags Conversations whose contact carries these tags
sortOrder desc (default, newest activity first) or asc
limit, cursor Up to 100 per page. Pass pagination.nextCursor back as cursor

Each conversation carries the participant (participantName, participantPicture, participantVerifiedType), lastMessage, updatedTime, unreadCount and threadControl. Conversations that started from an ad carry adAttribution with the ad, ad set and campaign names. Instagram conversations can carry instagramProfile (isFollower, isFollowing, followerCount, isVerified); treat a missing value as unknown, never as "not a follower".

To find a conversation by what was said, use GET /v1/inbox/conversations/search with query (up to 200 characters) and an optional direction.

#Read messages

GET /v1/inbox/conversations/{conversationId}/messages needs the accountId and returns messages oldest first by default (sortOrder=desc flips it). Pages hold up to 100 messages.

One message
{
  "id": "6650c1e2a3b4c5d6e7f81201",
  "conversationId": "6650c1e2a3b4c5d6e7f80b33",
  "platform": "instagram",
  "direction": "incoming",
  "message": "Hi, is the blue one still in stock?",
  "senderName": "Jane",
  "createdAt": "2026-10-29T14:00:03.000Z",
  "attachments": [],
  "deliveryStatus": null,
  "reactions": [],
  "isEdited": false,
  "isDeleted": false,
  "sentVia": null
}
  • direction is incoming or outgoing.
  • deliveryStatus on outgoing messages moves through sent, delivered, read, played (voice notes) or failed, where the platform reports it. deliveryError explains a failure.
  • attachments[] have a type (image, video, audio, file, sticker, share, template), a url and, for links that expire, a refreshUrl. GET .../attachments/{index} always resolves a fresh link (a redirect by default, or JSON with format=json).
  • Edited and unsent messages keep their history in editHistory, isDeleted and deletedAt.
  • sentVia names what sent an outgoing message: human, api, broadcast, sequence, workflow, comment_automation or bulk-api. null means unknown, for example a message sent from the platform's own app.

#Reply

POST /v1/inbox/conversations/{conversationId}/messages sends a message into an existing conversation. accountId is required; everything else depends on what you send.

curl -X POST https://api.pozy.ai/v1/inbox/conversations/6650c1e2a3b4c5d6e7f80b33/messages \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9b6c0f4e-1f0b-4b38-9a1e-6f2e8d7f1c55" \
  -d '{
    "accountId": "6650c1e2a3b4c5d6e7f80a22",
    "message": "Yes, the blue one is in stock. Want me to hold it?",
    "quickReplies": [
      { "title": "Hold it", "payload": "HOLD_BLUE" },
      { "title": "No thanks", "payload": "NO_THANKS" }
    ]
  }'

A 200 means Pozy accepted the message, not that it was delivered. Delivery arrives later as message.sent, then message.delivered and message.read (or message.failed) on platforms that report them.

#What you can send

Field What it sends Where
message Text Everywhere
attachmentUrl + attachmentType (image, video, audio, file) One attachment from a public HTTPS URL. attachmentName sets the file name Most DM platforms, not Bluesky or Reddit
quickReplies Up to 13 tappable chips (title up to 20 characters) Instagram, Facebook
buttons 1 to 3 url, postback or phone buttons under the text. Text is then capped at 640 characters. phone is Facebook only Instagram, Facebook
template A generic card carousel, up to 10 cards with title, subtitle, image and buttons Instagram, Facebook
interactive Lists, call-to-action URLs, flows, product messages, location and contact requests WhatsApp
replyTo The platform message id to quote. On Slack it threads the reply instead WhatsApp, Telegram, Slack
replyMarkup Inline or reply keyboards Telegram
voiceNote Send an OGG/Opus audio attachment as a voice note WhatsApp
location, contacts A location pin, or contact cards WhatsApp (location also on iMessage)
sharedMediaId, sticker Share one of the account's own posts, or send the heart sticker, on their own Instagram
subject, effect, contactCard A bold title line, a screen effect, or your contact card iMessage
channel Force one send over sms, rcs or whatsapp instead of letting the number pick iMessage numbers with those add-ons
cc Extra recipients on the reply Email threads from connected Google or Microsoft mailboxes

A postback button with a payload of pozy:workflow:<workflowId> starts that workflow when tapped. See Workflows.

Upload a local file first with POST /v1/media/upload-direct (multipart) and send the returned url as attachmentUrl.

#Messaging windows

Platforms limit when a business can write to a person:

Platform Rule
Instagram, Facebook Free-form replies within 24 hours of the person's last message. Up to 7 days with messagingType: "MESSAGE_TAG" and messageTag: "HUMAN_AGENT", for replies typed by a person only
WhatsApp Free-form messages within the 24-hour customer service window. Outside it, send an approved template. See WhatsApp
TikTok Reply only. Up to 10 messages within 48 hours of the person's last message
X, Bluesky, Telegram No window, but the person must accept DMs from you

HUMAN_AGENT is refused on automated sends (workflows, sequences, broadcasts). The other message tags were retired by Meta and fail.

#Start a conversation

POST /v1/inbox/conversations opens a new conversation, or appends to the existing one with that person. Name the recipient with participantId or participantUsername.

  • X: Pozy checks that the person accepts DMs from your account first and answers 422 with code DM_NOT_ALLOWED when they do not. Pass skipDmCheck: true if you already verified it. X DMs are billed per call (pricing).
  • WhatsApp: a first message must be a template: pass templateName, templateLanguage, templateParams and, where the template has them, headerMedia and templateButtonParams.
  • TikTok: businesses cannot start conversations.

#Manage conversations

Task Call
Archive or unarchive PUT /v1/inbox/conversations/{id} with status: "archived" or "active"
Mark as read POST /v1/inbox/conversations/{id}/read
Show "typing..." POST /v1/inbox/conversations/{id}/typing
React to a message POST /v1/inbox/conversations/{id}/messages/{messageId}/reactions with an emoji; DELETE to remove
Edit a sent message PATCH /v1/inbox/conversations/{id}/messages/{messageId} (Telegram text and keyboards)
Delete a message DELETE /v1/inbox/conversations/{id}/messages/{messageId} where the platform allows unsending
Label a conversation Create Page labels with POST /v1/inbox/labels, then attach them with POST .../labels (Facebook)
Delete a conversation DELETE /v1/inbox/conversations/{id}

#Message requests

On Facebook, DMs from people the Page has not accepted wait in a requests folder. List them with folder=requests and accept one with POST .../accept, which sends your first message and moves the thread into the inbox. Instagram does not expose the folder (the list answers 400 with PLATFORM_LIMITATION), but accepting still works, and requests arrive as message.received once the person writes.

#Hand a conversation to another app

On WhatsApp, Facebook and Instagram, another app can own a thread, for example Meta's own inbox app or a business agent. While it does:

  • Incoming messages still arrive as message.received, flagged metadata.standby: true. Pozy runs no automation on them.
  • Your sends answer 409 with code not_thread_owner.

Move the thread with POST /v1/inbox/conversations/{id}/thread-control:

action Effect
pass Give the thread to targetAppId (or target: "ai_agent"), with an optional metadata note
take Take it back. Meta allows this only to the Page's primary receiver
request Ask the current owner to pass it. Nothing changes until they do
release Return it to the primary receiver

conversation.control_changed fires whenever the owner changes. On WhatsApp, sending any message takes control back, so a bot that answers every message.received must skip standby messages.

#When a send fails

Response Meaning What to do
400 PLATFORM_LIMITATION The platform cannot do what you asked, for example an attachment on Bluesky Send without it
409 not_thread_owner Another app owns the thread Take the thread back first
422 DM_NOT_ALLOWED X recipient does not accept your DMs Do not retry
500 The platform failed the send. It may or may not have gone out List the conversation's messages before retrying

Send an Idempotency-Key with every reply. If the response is lost after a successful send, a retry with the same key returns the original response instead of a second message. A failed (non-2xx) request releases the key, so after an ambiguous 500, check the messages first; an empty result is inconclusive, not proof it was not sent. See Idempotency.

#Endpoints

Endpoint What it does
GET /v1/inbox/conversations List conversations
POST /v1/inbox/conversations Create conversation
GET /v1/inbox/conversations/search Search conversations
GET /v1/inbox/conversations/{conversationId} Get conversation
PUT /v1/inbox/conversations/{conversationId} Update conversation status
DELETE /v1/inbox/conversations/{conversationId} Delete conversation
GET /v1/inbox/conversations/{conversationId}/messages List messages
POST /v1/inbox/conversations/{conversationId}/messages Send message
PATCH /v1/inbox/conversations/{conversationId}/messages/{messageId} Edit message
DELETE /v1/inbox/conversations/{conversationId}/messages/{messageId} Delete message
POST /v1/inbox/conversations/{conversationId}/typing Send typing indicator
POST /v1/inbox/conversations/{conversationId}/thread-control Change who answers a conversation (handover)
POST /v1/inbox/conversations/{conversationId}/accept Accept a message request
GET /v1/inbox/labels List the Page's conversation labels
POST /v1/inbox/labels Create a conversation label
DELETE /v1/inbox/labels/{labelId} Delete a conversation label
GET /v1/inbox/conversations/{conversationId}/labels List the labels on a conversation
POST /v1/inbox/conversations/{conversationId}/labels Add a label to a conversation
DELETE /v1/inbox/conversations/{conversationId}/labels/{labelId} Remove a label from a conversation
POST /v1/inbox/conversations/{conversationId}/read Mark a conversation as read
POST /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions Add reaction
DELETE /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions Remove reaction
POST /v1/media/upload-direct Upload media file
GET /v1/inbox/conversations/{conversationId}/messages/{messageId}/attachments/{index} Resolve message attachment

#Webhook events

Event When it fires
message.received Message received.
conversation.control_changed Conversation control changed.
conversation.started Conversation started.
message.sent Message sent.
message.edited Message edited.
message.deleted Message deleted.
message.delivered Message delivered.
message.read Message read.
message.played Message played.
message.failed Message delivery failed.
reaction.received Reaction received.
referral.received Referral received.