---
title: "List messages"
method: GET
path: "/v1/inbox/conversations/{conversationId}/messages"
tags: ["Messages"]
---

# List messages

`GET /v1/inbox/conversations/{conversationId}/messages`

Fetch messages for a specific conversation, with cursor-based pagination
and ordering control.

Pagination: pass `pagination.nextCursor` from a prior response back as
the `cursor` query param to fetch the next page. The cursor is opaque;
do not parse or construct it client-side.

Sort order: defaults to `asc` (oldest first, chat style). For the
"show me the latest messages" pattern, pass `?sortOrder=desc&limit=N`.
X, Instagram, Telegram, WhatsApp and Reddit honor the requested
order from the local message store. For Facebook and Bluesky, the
upstream APIs only return newest-first and have no order parameter, so
sort order is best-effort and only reverses items within a single page
(pages still walk newest→oldest). The response field `sortOrderApplied`
tells you what was actually applied.

Reddit threads are paginated client-side because Reddit's API has no
per-thread cursor. Very long threads may be upstream-truncated by
Reddit's inbox/sent windows (~100 most-recent items each); this is a
Reddit platform limitation.

Instagram and Facebook conversations include history from before the
account was connected, replayed from Meta. That replay covers the 500
most recent messages per conversation: a longer thread keeps its newest
500 and older messages are not retrievable. Messages that arrived after
the account was connected are unaffected. Replayed messages are stored
as already read and emit no webhooks.

X limitation: X's encrypted "X Chat" messages are not accessible via the API. Conversations where the other participant uses encrypted X Chat may only show your outgoing messages. See the list conversations endpoint for more details.

This endpoint is read-only and does NOT mark messages as read or send
read receipts. To mark a conversation read (and send WhatsApp blue ticks
on eligible accounts), call `POST /v1/inbox/conversations/{conversationId}/read`.

## Path parameters

- `conversationId` string, required

## Query parameters

- `accountId` string, required
- `limit` integer
- `cursor` string
- `sortOrder` 'asc' | 'desc'

## Response `200`

Messages in conversation

- object
  - `status` string
  - `pagination` object
    - `hasMore` boolean — Whether more messages are available beyond this page.
    - `nextCursor` string, nullable — Opaque cursor to fetch the next page. `null` on the last page.
  - `sortOrderApplied` 'asc' | 'desc' — Sort order actually applied to the returned page. May differ from the requested `sortOrder` for Facebook and Bluesky (always `desc` regardless of request).
  - `messages` object[]
    - `id` string — The platform's own message id: the `wamid` on WhatsApp, the `mid` on Instagram and Facebook Messenger. This is what `metadata.quotedMessageId` points at, the value to pass as `replyTo` on the platforms that support quote-replies, and the `{messageId}` segment of the attachment-resolve URL. Webhooks deliver the same value as `message.platformMessageId`; this response has no field by that name.
    - `conversationId` string
    - `accountId` string
    - `platform` string
    - `message` string
    - `senderId` string
    - `senderName` string, nullable
    - `senderVerifiedType` 'blue' | 'government' | 'business' | 'none', nullable — X verified badge type. Only present for X messages.
    - `direction` 'incoming' | 'outgoing'
    - `createdAt` string, date-time
    - `attachments` object[]
      - `id` string
      - `type` 'image' | 'video' | 'audio' | 'file' | 'sticker' | 'share' | 'template' | 'unsupported_type' — On Instagram and Facebook a shared reel, post or ad reaches Zernio with no type of its own (Meta's `unsupported_type`) and is resolved from the media's Content-Type at ingest into `image`, `video`, `audio` or `file` with `originalType: "unsupported_type"`. `unsupported_type` is returned only when the CDN could not be classified in time.
      - `originalType` string — Instagram and Facebook only, and present only when it differs from `type`. Meta's own type before normalization: `ig_reel` and `reel` become `video`, while `ig_post`, `post`, `ig_story` and `story_mention` become `share`. A story mention is `type: "share"` with `originalType: "story_mention"`; render on this field, since `share` alone is ambiguous. `originalType: "unsupported_type"` marks a share Meta did not classify, resolved by content type: `refreshUrl` cannot re-mint it (Meta answers `is_unsupported` with no attachments for the message node), so download it when the `message.received` webhook arrives.
      - `mimeType` string — MIME type of the media when Zernio knows it. On Instagram and Facebook it is set for shares resolved by content type (`originalType: "unsupported_type"`).
      - `url` string — Direct media link. On Instagram and Facebook this is a signed Meta CDN url that EXPIRES: use it now, do not store it. Persist `refreshUrl` instead.
      - `refreshUrl` string, nullable — Instagram and Facebook only. Endpoint that resolves this attachment to a working url every time, re-minting it from Meta when the stored one has expired. Safe to store and render indefinitely, except for an attachment with `originalType: "unsupported_type"`: Meta cannot re-serve those, so the endpoint answers 404 once the url has expired.
      - `filename` string, nullable
      - `previewUrl` string, nullable
      - `payload` object — Template content (title, subtitle, image, buttons) when type is template
    - `subject` string, nullable — Reddit message subject
    - `storyReply` object — Instagram only. Present when the message replies to one of the account's stories. Also set on history imported after connecting, read off Meta's `story.reply_to`.
      - `storyId` string — The Instagram story ID the user replied to.
      - `storyUrl` string — Meta CDN URL for the story media. Expires roughly 24 hours after the story posted; fetch promptly or treat 404s as expected.
    - `isStoryMention` boolean — Instagram only. True when the message is the user mentioning the account in their own story. The story itself is the `share` attachment with `originalType: "story_mention"`. Also set on imported history, read off Meta's `story.mention`.
    - `noRenderableContent` boolean — Instagram and Facebook Messenger only. True when Meta withholds the content of this message from the API (its `is_unsupported` flag): `message` is empty, `attachments` is empty, and there is nothing to fetch, now or later. Distinguishes such a message from an ordinary empty one such as a story reply with no text. Absent on other platforms.
    - `isEdited` boolean — True if the sender has edited this message at least once.
    - `editedAt` string, date-time, nullable — When the most recent edit happened.
    - `editCount` integer — Total number of edits applied.
    - `editHistory` InboxMessageEditHistoryEntry[] — Every prior version of the message, oldest first.
      - `text` string, nullable, required
      - `attachments` InboxMessageEditAttachment[], required
        - `type` string
        - `url` string
        - `payload` object
      - `editedAt` string, date-time, required
    - `isDeleted` boolean — True if the sender has deleted (unsent) this message. The original message and attachments fields remain populated.
    - `deletedAt` string, date-time, nullable
    - `deliveryStatus` 'sent' | 'delivered' | 'read' | 'failed' | 'deleted', nullable — Lifecycle status for outgoing messages. Not all platforms emit every state (see webhook support matrix).
    - `deliveredAt` string, date-time, nullable
    - `readAt` string, date-time, nullable
    - `sentAt` string, date-time, nullable — Original send time for outgoing messages (used for Messenger watermark queries).
    - `deliveryError` object, nullable — Populated when deliveryStatus === "failed".
      - `code` integer
      - `title` string
      - `message` string
      - `details` string — Platform's extended detail for `code` (WhatsApp: Meta's `error_data.details`), when the platform sent one. Absent on SMS.
      - `href` string, uri — Link to the platform's documentation for `code`, when the platform sent one.
    - `reactions` object[] — Emoji reactions on this message (WhatsApp / Telegram). At most one per party in a 1:1 thread.
      - `emoji` string
      - `fromMe` boolean — true if the connected account reacted, false if the contact did.
      - `reactedAt` string, date-time
    - `metadata` object — Platform-specific extras. Free-form, but commonly includes: `quotedMessageId` (the `id` of the message this one replies to, delivered as `message.platformMessageId` on webhooks), `waInteractive` (a compact descriptor of WhatsApp interactive content sent: buttons / list / cta_url / flow / location_request), `metaInteractive` (outgoing Instagram/Facebook buttons, quickReplies and template, as sent), and for inbound interactive taps `interactiveType` / `interactiveId`. It can also carry `source` (`whatsapp_business_app` / `coexistence_history` on a WhatsApp Coexistence number, `bulk-api` on a POST /v1/whatsapp/bulk send), which is where the message reached us from rather than who produced it: read `sentVia` for that.
    - `sentVia` 'human' | 'api' | 'broadcast' | 'sequence' | 'workflow' | 'comment_automation' | 'bulk-api' | 'null', nullable — Which Zernio surface produced this outgoing message: `human` (an operator in the Zernio inbox), `api` (a call to this API), `broadcast`, `sequence`, `workflow`, `comment_automation`, or `bulk-api` (POST /v1/whatsapp/bulk). Same vocabulary as the `source` filter on the inbox analytics endpoints. Always present, and `null` whenever the lineage is unknown: every incoming message, any outgoing message sent from the platform's own app, and every message stored before this field shipped (2026-08). Existing messages are NOT backfilled, so treat `null` as "unknown", never as "sent by a human".
  - `lastUpdated` string, date-time

## Other responses

- `400` — Invalid request
- `401` — Unauthorized
- `403` — Inbox addon required
- `502` — The platform returned a server error.
- `503` — An upstream service or database is temporarily unavailable. Retry after the indicated delay. A timed-out write may have completed upstream; check its outcome before resubmitting.

## Changes

- **2026-09-25** `a0d8f21b5abe` — 1 breaking, 1 warning, 4 info
  - the `messages/items/storyReply` response's property type changed from `boolean, null` to `object` for status `200`
  - added the new `unsupported_type` enum value to the `messages/items/attachments/items/type` response property for the response status `200`
  - added the optional property `messages/items/attachments/items/mimeType` to the response with the `200` status
  - added the optional property `messages/items/noRenderableContent` to the response with the `200` status
  - …2 more
- **2026-09-17** `fdaf9a9fb1a7` — 1 warning, 1 info
  - added the new `template` enum value to the `messages/items/attachments/items/type` response property for the response status `200`
  - added the optional property `messages/items/attachments/items/payload` to the response with the `200` status
- **2026-09-16** `3e6ddf2a99ea` — 3 info
  - added the optional property `details/budgetScope` to the response with the `400` status
  - added the optional property `details/budgetScope` to the response with the `502` status
  - added the optional property `details/budgetScope` to the response with the `503` status
- …earlier changes not shown

[Full history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/inbox/conversations/:conversationId/messages/get.md)

---

[API](https://skmtc.dev/zernio/apis/zernio-api.md) · [All operations](https://skmtc.dev/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zernio/apis/zernio-api/revisions/a0d8f21b5abe?raw)
