Skip to content
Linq

List chat transcript

GET/v1/chats/{chat}/transcript

Lists the chat as a conversation to display: what the customer saw, in order.

Behavior

  • The same rows /events pages, rebuilt for rendering: each carries a kind to switch on, the message’s canonical parts, and the delivery state of each outbound message.
  • Same paging (after_seq, limit) and same bounds as /events. Use this to draw a chat; use /events to rebuild state.
Path ParametersExpand Collapse
chat: string
Query ParametersExpand Collapse
after_seq: optional number

Return rows with seq greater than this; omitted (or non-numeric) reads from the start.

formatint64
limit: optional number

Page size; absent or outside 1–100 reads as 100.

formatint32
ReturnsExpand Collapse
data: array of TranscriptRow { actor, created_at, event_type, 13 more }
actor: string

Who wrote it: customer, partner, human, flow, system or brand. On a row carrying a message_id this is who COMPOSED that message: a human agent replying from a console reads human, a deterministic flow reads flow, and NOT the actor of the underlying event, which is always partner on a send because that event means we delivered it. Everywhere else it is the underlying event’s own actor.

created_at: string
formatdate-time
event_type: string

The underlying event type (message_received, owner_changed, consent_send_refused, …); render detail from it, but branch on kind.

kind: "customer_message" or "business_message" or "system"

What KIND of thing happened; switch on this. The set is CLOSED: an event type added later renders as system, never as a fourth value.

One of the following:
"customer_message"
"business_message"
"system"
seq: number

The seq this row is built from; same numbering as the events endpoint, and the transcript’s own cursor.

formatint64
attachments: optional array of object { att_id, mime_type, name, 2 more }

Files the CUSTOMER sent with this message; absent when they sent none.

att_id: optional string

The retained plaintext’s id (att_…); absent when nothing was retained.

mime_type: optional string
name: optional string

The file’s name as the customer’s device sent it.

reason: optional string

Present exactly when att_id is absent, and a CLOSED set: too_large | brand_unresolved | store_unavailable | disabled | unavailable (any failure earlier than the retention decision). Never the underlying error text, that is free-form and can carry the channel’s own temporary download URL, so it stays on /events.

size: optional number

Size in bytes.

formatint64
delivery: optional string

That message’s delivery state, read from the message row: queued | sending | sent | retry | failed | undelivered | suppressed. Two of those are terminal failures, not one: undelivered is the “resubmitting later may land” outcome and failed the one that will not. There is NO delivered or read receipt here or anywhere on this API; AMB produces neither, and sent means only that Apple accepted the send.

form_response: optional unknown

A form_response row’s FULL record, first-class: values keyed by your original page ids, item_ids, status, sources, opened_at, closed_at. Present only on event_type: "form_response" rows.

group_id: optional string

Ties a fan-out member to its group, and a form_response row to the degraded prompt messages that asked, so grouping needs no payload spelunking. Present on rows that belong to a group.

message_id: optional string

The outbound message this row is about; pass it to GET /v1/messages/{msg}.

operator: optional string

WHICH human. On a row carrying a message_id this is the operator who composed that send, where one did and where the send named them. On every other row it is the events endpoint’s operator: the human we recorded, with the same rules.

On message rows this deliberately does NOT match /events, which shows no operator for the same seq: a send is recorded by the worker that delivered it, with actor partner and no operator, so the composer is only knowable from the message row. Absence never means “no human”; it means this row cannot say which.

parts: optional array of unknown

The message’s canonical parts (the same array a send takes), absent on system rows and on a message that carried none: an invitation delivery records its invitation identity rather than parts. Typed as an opaque array rather than Part[] on purpose: the generated Go transport must never contain a second decoder for the part union (content.ParseParts is the one decode door), and a response $ref to Part would put one there.

reaction: optional object { action, correlation, kind, 7 more }

The reaction this customer text row IS, when the text is a tapback. The row keeps its text verbatim and kind stays customer_message, so a client ignoring this key sees exactly the transcript it saw before.

action: "added" or "removed"

Whether the customer APPLIED this reaction or TOOK IT AWAY. CLOSED: added or removed, and always present. Apple sends a removal as its own message, so a removal is its own transcript fact rather than the absence of one. Note what this does NOT tell you: replacing one reaction with another sends the new added and no removed beside it, so state is last-one-wins per (message, customer) and a client that appends will accumulate reactions the customer no longer has.

One of the following:
"added"
"removed"
correlation: "inferred" or "ambiguous" or "unmatched" or "unavailable"

HOW the target was reached. CLOSED: inferred (exactly one message in the recent window carried that text, and both target keys name it), ambiguous (several did; candidate_count says how many, and no winner is picked for you), unmatched (none did), unavailable (Apple named no target at all, which is what a reaction to a non-text message looks like). There is no exact, because no reaction on this wire carries an id.

One of the following:
"inferred"
"ambiguous"
"unmatched"
"unavailable"
kind: "like" or "love" or "dislike" or 5 more

What the customer did. CLOSED: like, love, dislike, laugh, emphasize, question, sticker (a sticker or Memoji), or emoji (any other character, which rides emoji).

One of the following:
"like"
"love"
"dislike"
"laugh"
"emphasize"
"question"
"sticker"
"emoji"
attachment_id: optional string

The sticker’s image, on a sticker whose image we retained: pass it to GET /v1/attachments/{att}/content. Present ONLY when kind is sticker and action is added. Absent when the image was not retained, or when more than one file arrived and the wire does not say which is the sticker: no handle is better than the wrong one. Do not assume a format. Captures show both image/png and image/heic; read the response’s own content type. A PRESENT ID IS NOT A PROMISE OF BYTES, exactly as for att_id: retained attachments are swept after their published TTL and the event is immutable, so an old reaction still names the id it was minted with and the content read answers 404. Handle that; it is the ordinary outcome for anything old enough.

candidate_count: optional number

How many messages carried that exact text, on ambiguous.

formatint32
emoji: optional string

The character the customer picked, present only when kind is emoji, where the character IS the reaction and no named one exists for it.

quoted_text: optional string

The original message’s text as Apple quoted it back: the only handle this wire offers, and what the correlation is made against. Absent when Apple named no target.

target_msg_id: optional string

The message reacted to, on inferred. Pass it to GET /v1/messages/{msg}.

target_seq: optional number

That message’s seq, on inferred: the transcript row to scroll to.

formatint64
target_side: optional "business" or "customer"

WHOSE message was reacted to, on inferred: business or customer. A customer can react to their own message, so this is not derivable from the reaction being a customer event. Unlike the correlation it is not an inference; it is the matched row’s own type.

One of the following:
"business"
"customer"
reason: optional string

The reason the event states, where it states one: the failure discriminator on a failed or suppressed send, the handoff reason on an ownership change, the refusal reason on a consent row.

reply: optional unknown

A customer’s normalized interactive selection (quick reply, list pick, time slot); absent when they sent plain text. A customer who taps a Quick Reply sends no text, so this is the row’s only content.

request_id: optional string

The requestIdentifier this message was delivered under: the exact value an interactive reply names in its own request_id, so an answer can be matched to the message it answers instead of inferred from item ids (the same card sent twice defines the same item ids, while each interactive send is delivered under a fresh identifier). Present only on a row naming a message that recorded one: interactive sends mint one, plain sends do not, and rows from before this field was served carry none.

List chat transcript

curl https://messages.api.linqapp.com/v1/chats/$CHAT/transcript \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY"
{
  "data": [
    {
      "seq": 13,
      "kind": "customer_message",
      "event_type": "message_received",
      "actor": "customer",
      "created_at": "2026-08-06T14:01:58Z",
      "parts": [
        {
          "type": "text",
          "body": "where is my driver?"
        }
      ]
    }
  ]
}
Returns Examples
{
  "data": [
    {
      "seq": 13,
      "kind": "customer_message",
      "event_type": "message_received",
      "actor": "customer",
      "created_at": "2026-08-06T14:01:58Z",
      "parts": [
        {
          "type": "text",
          "body": "where is my driver?"
        }
      ]
    }
  ]
}