API RU
Early access. API keys are issued on request. How to get a key

Read messages in both directions within one campaign and check the period for which the data is complete.

New features are enabled for your organization on request.

#What the conversation includes

GET /v1/leads/{id}/messages?view=conversation returns available messages starting with our first confirmed message to this lead in this campaign, including that first message. It covers campaign steps, manual outbound messages and individual incoming messages, including attachments without text.

A lead is one contact's participation in one campaign. Earlier conversations and messages belonging to another campaign are excluded. If a message cannot be attributed to this conversation, the API does not guess. A lead may have several dialogs with different accounts; completeness for each is in meta.dialogs.

The first available copy of a message is saved. Edits and deletions are not tracked. A message deleted before it could be saved may be missing even from a checked range. complete does not mean that every message that ever existed has been recovered.

#Request and message fields

You need messages:read and "Message text" enabled on the key. Without view, the route keeps its existing response format and cursors.

curl
curl -G https://api.melioralab.io/v1/leads/5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31/messages \
  -H "Authorization: Bearer $MELIORA_API_KEY" \
  -d view=conversation -d limit=50

The response contains object, data, has_more, next_cursor and meta. Each message includes:

  • id: a stable identifier. Merge repeated results by this ID.
  • workspace_id, campaign_id, lead_id, account_id: the message context.
  • direction: inbound or outbound; author: campaign, manager, lead or unknown.
  • state: sent, received or unknown. An unknown outcome does not mean the send failed.
  • sent_at: the message time or null; observed_at: when the message was saved.
  • media_kind: the content kind; attachment: an attachment description or null.
  • text, text_truncated, text_unavailable, text_unavailable_reason: saved text and its availability.

Text is limited to 4000 characters. text_truncated=true means it was shortened, false means it is complete, and null means this is unknown. When text is unavailable, text=null; the reason is not_saved, ownership_unproven, source_conflict or purged. Text availability and completeness of the message set are separate measures.

See the conversation reference for full schemas and fictional responses.

#Attachments

attachment contains only {kind, caption}. Kinds are photo, video, voice, video_note, file, sticker and other. Audio files use file; unsupported kinds use other. A caption is limited to 4000 characters or is null; text availability and truncation indicators also apply to the caption. When text is unavailable, the caption is also null.

Files, download links, filenames and contact card contents are not returned.

#Reading completeness

meta.completeness describes the lead's available range as a whole. meta.dialogs[].completeness describes each dialog separately.

  • overall, inbound, outbound: overall completeness and completeness in each direction. Values are complete, partial and unknown.
  • boundary_at: the time of our first confirmed campaign message. This is the inclusive start of the range.
  • checked_at: when the range was checked; checked_through: the inclusive time through which it was checked. These are different values. null means a value is unknown.
  • available_from, available_to: the boundaries of available saved data. They do not prove completeness by themselves.
  • observed_at: when completeness information was read.
  • gaps: up to 50 potentially incomplete intervals with id, from, to and state. An open interval has to=null.
  • gaps_truncated=true: the gap list was shortened. An empty gap list does not prove completeness.

complete means both directions were checked from the beginning through checked_through, as of checked_at. Messages arriving after the check are outside that promise. partial means a known part is incomplete, such as a gap or an unavailable period. unknown means there is not enough evidence, for example because the starting boundary has not been established.

A gap has state open, recovering, recovered, unrecoverable or unknown. recovered means that particular gap is closed; still check overall for the complete picture.

#Handling partial data

  1. Save the returned messages and the incomplete state. Do not treat a missing message as proof that it never existed.
  2. Read all pages in the current traversal. has_more=false only means there are no more pages of saved data.
  3. Later, start again without cursor, keeping view=conversation, and merge messages by id. Compare completeness and checked boundaries for each dialog.
  4. If a gap remains unrecoverable, keep the incomplete state. If completeness does not change for a long time, contact support with the Request-Id.

For unknown, also preserve the uncertainty and retry later without treating the history as complete.

#Cursors and snapshot_stale

The first page fixes the data set for a traversal. Pages run from earlier to later messages; the cursor continues the same data set. A message added later, including one with an earlier timestamp, appears in a new traversal.

Send view=conversation on every page. You may change limit: the default is 50 and the maximum is 100. A cursor is bound to the lead, view and key scope; a cursor from the existing view cannot be used here. Cursors last 15 minutes. On 410 cursor_expired, start again. On 400 cursor_mismatch, check the parameters and scope.

If any page returns meta.snapshot_stale=true, new facts appeared after the traversal began. Finish the current traversal, then start again without a cursor and merge results by id. snapshot_stale=false does not replace checking meta.completeness.

#Incoming events and webhooks

message.received means one individual incoming message, including one without text. data.message.id matches the conversation message ID. data.backfilled=true indicates a recovered gap; the message keeps its original timestamp. Events do not replace reading the available conversation.

For the feed, select the 2b catalog or type=message.received. In the feed, request text and captions with include_text=true. New webhooks carry metadata only; read text through this API route.