Skip to content
Linq

Activity

A chat is a conversation between one customer and your brand. It starts when a customer messages you, or when they accept an invitation you sent. No endpoint creates one directly, and you can’t message someone who has never contacted you. Invitations are the one way to reach a customer first, and they send an Apple-templated card rather than a message you write.

A chat is identified by its chat_id, like chat_2d4f8a91-6c03-47be-b5d2-9e81c3f60a27. Treat it as an opaque string, don’t parse it, and don’t depend on its prefix or length.

A chat_id belonging to another brand returns 404, the same response you get for one that doesn’t exist. You can’t tell the two apart, which is deliberate.

GET /v1/chats/{chat} returns the full object. The fields that govern whether you can send:

state; open or closed. A customer deletes a conversation on their device, and the chat closes. Sends to a closed chat return 409 chat_closed. A new inbound message reopens it.

owner, who currently holds the conversation: partner, flow, human_pending, or human. Only one of them holds it at a time, and only the holder can send. Sending while owner is anything but partner returns 409 chat_owned, which is what stops your automation from talking over a human agent mid-sentence.

POST /v1/chats/{chat}/handoff passes the conversation to someone else.

?force=true is on the SEND, not the handoff: it sends into a chat you do not own and records that you overrode the gate (chat.send_forced). It does not override consent: a customer who opted out still returns 403 consent_required.

capabilities; what the customer’s device told us it can display, as of their most recent message. An empty array means we don’t know yet, not that the device supports nothing.

send_blocked; whether a send would be refused right now, and with which error:

Response

{"send_blocked": {"blocked": true,
                  "code": "chat_owned",
                  "message": "a human agent holds this chat"}}

We run the same checks, in the same order, that a real send runs. So code and message are exactly what you would get back if you tried to send.

"blocked": false means nothing about the chat itself would refuse you. It is not a promise that your message will be accepted. Anything that depends on the message: its size, or whether the customer’s device can render the parts you composed; is checked when you send it.

Listing

GET /v1/chats returns your chats newest-first, a page at a time. Pass the previous response’s next_cursor back verbatim; the list ends when it comes back as an empty string. Filters are optional: state, owner, assigned_operator.


Reading a chat

GET /v1/chats/{chat}/transcript returns what the customer saw, in order.

Response

{"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?"}]}]}

Decide what to render from kind. It has three values and will never have more: customer_message, business_message, and system. When we add a new event type in a later release it arrives as system, so a switch you write today keeps working.

event_type says what the event actually was (message_received, owner_changed, consent_send_refused). Use it to fill in the detail once you’ve decided how to render the row, but don’t build your control flow on it, because new values appear here over time.

seq numbers the events in a chat and never skips. Page with ?after_seq=<the last seq you saw>&limit=…. GET /v1/chats/{chat}/events returns the same events without the transcript’s formatting.

Get a chat's activity timeline
GET/v1/chats/{chat}/activity
ModelsExpand Collapse
ActivityResponse object { activity, consent, consent_events, origin }

The activity timeline: the chat’s governance story as a read-time composition. origin, consent, and consent_events appear on the first page only; a resumed page (after_seq present) carries activity alone.

activity: array of object { actor, at, class, 3 more }

The governance events, seq-paged exactly as /events.

actor: string

Who did this: customer, partner, human, flow, system, or brand.

at: string
formatdate-time
class: "contact" or "consent" or "control"

Server-promised closed class; render by class for any type you do not map.

One of the following:
"contact"
"consent"
"control"
seq: number

The event log’s own seq: the same id the /events row carries.

formatint64
type: string

The event type (owner_changed, handoff_accepted, flow_entered, …).

detail: optional unknown

Per-type detail from the event’s payload. OPEN AND ADDITIVE by contract: keys may be added over time, never renamed or removed; operator, reason and the per-type ids are present whenever their event recorded them.

One of the following:
origin: optional object { kind, at, invitation }

Contact provenance. First page only.

kind: "customer_initiated" or "invitation"

customer_initiated (the customer’s first message created the chat) or invitation (business-initiated).

One of the following:
"customer_initiated"
"invitation"
at: optional string

First-contact instant for a customer-initiated chat: the chat’s own creation stamp.

formatdate-time
invitation: optional object { id, delivery, sent_at, 3 more }

The originating invitation, for kind: invitation.

id: string
delivery: string

Its delivery state.

sent_at: string

When the invitation was sent.

formatdate-time
response: optional string

The customer’s response, when one was recorded.

response_source: optional string

How the response was resolved: tap, or the literal-text fallback.

superseded_by_chat_id: optional string

Set when a later invitation superseded this conversation.