## List chats

**get** `/v1/chats`

Lists your brand's chats, newest first, one page at a time.

**Behavior**

- Scoped to your API key's brand by construction: no parameter names a
  brand, and another brand's chats are invisible rather than forbidden.
- Page with `cursor`: pass the previous response's `next_cursor` back
  verbatim, and stop when it comes back empty.

### Query Parameters

- `assigned_operator: optional string`

  Filter by the operator holding the chat; absent means every row, assigned or not.

- `cursor: optional string`

  The previous page's `next_cursor`, verbatim; absent starts from the first page. Not a cursor this API issued returns HTTP 400 `code` 1013.

- `limit: optional number`

  Page size; absent (or non-numeric) reads as 25, above 100 reads as 100.

- `owner: optional string`

  Filter by turn owner (`partner`, `flow`, `human_pending`, `human`); absent means every owner.

- `state: optional string`

  Filter by lifecycle phase (`open`, `closed`); absent means both.

### Returns

- `data: array of object { capabilities, chat_id, customer_handle, 10 more }`

  - `capabilities: array of string`

    The device's most recently announced capability tokens. Empty means unknown, never "supports nothing".

  - `chat_id: string`

    Chat id (`chat_…`); pass it to `GET /v1/chats/{chat}` for the full view.

  - `customer_handle: string`

    The customer identifier: an Apple Opaque ID, or canonical `tel:+E164`, per `handle_kind`.

  - `handle_kind: "opaque" or "tel"`

    Which identifier kind `customer_handle` holds; branch on this, never read the string itself.

    - `"opaque"`

    - `"tel"`

  - `origin: "customer" or "invitation"`

    How the chat began. Not derivable from `handle_kind`: an accepted invitation arrives under a brand-new opaque id.

    - `"customer"`

    - `"invitation"`

  - `owner: string`

    Turn owner: `partner`, `flow`, `human_pending` or `human`. `human_pending` is the queue.

  - `owner_since: string`

    When the chat entered its CURRENT owner state: the queue clock, NOT `updated_at` (which any progress bumps). Stamped only when the owner actually changes; see `GET /v1/chats/{chat}` for the one self-clearing exception.

  - `state: string`

    Lifecycle phase: `open` or `closed`.

  - `updated_at: string`

    Last change of any kind: the list's sort key, and the first half of the cursor.

  - `assigned_operator: optional string`

    The operator holding this chat; absent when nobody does (the common case).

  - `business_id: optional string`

    The Apple business UUID your brand currently resolves to on this channel: the channel binding of record, present only when one is on file. The same value `GET /v1/chats/{chat}` serves.

  - `last_inbound_at: optional string`

    When we last heard from the customer; any inbound, a message or a chat close. ABSENT when they have never written (an invitation nobody has answered yet).

  - `last_message: optional object { actor, at, has_attachment, 2 more }`

    The newest message on this chat, so the row has a subject line instead of
    only a handle and a wait age. ABSENT when the chat has nothing
    previewable: an invitation nobody has answered, a chat whose only events
    are ownership changes, or a message that decoded to nothing. Render
    nothing in that case; never invent a preview.

    - `actor: string`

      Who spoke: `customer` on an inbound, `partner` on an outbound. It is the
      event log's actor, not the composer: a send is recorded by the worker
      that delivered it, so a reply a human agent typed still reads `partner`
      here. `GET /v1/chats/{chat}/transcript` is where a human is named.

    - `at: string`

      When that message was recorded. Distinct from both of its neighbours on
      this row: `last_inbound_at` moves only on inbound, and `updated_at` moves
      on any machine progress. Do not treat the three as one clock.

    - `has_attachment: boolean`

      Whether the message carried files. Deliberately ORTHOGONAL to `kind`,
      because the two answer different questions: `kind` says what to write on
      the row and this says whether to badge it. `kind: "text"` with this `true`
      is words PLUS a file: the one multi-part combination Apple admits.

    - `kind: "text" or "attachment" or "interactive" or "reply"`

      What kind of thing it was: a CLOSED set. `text`: there were words.
      `attachment`: there were none, only files. `reply`: the customer answered
      an interactive message instead of typing. `interactive`: the business sent
      one. Words win when a message has both.

      - `"text"`

      - `"attachment"`

      - `"interactive"`

      - `"reply"`

    - `preview: optional string`

      The opening words, with attachment placeholders (`￼`) removed and a single
      `…` where it was cut. ABSENT rather than empty when no words survived: an
      attachment-only message has a body of exactly `￼`, and a preview built
      from it verbatim would put an invisible character on the row. Say "sent a
      file" in your own words instead; this API does not ship desk copy.

- `next_cursor: string`

  Pass back as `?cursor=` for the next page. EMPTY when this page exhausted the list, that, not an empty `data`, is how paging ends.

### Example

```http
curl https://messages.api.linqapp.com/v1/chats \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY"
```

#### Response

```json
{
  "data": [
    {
      "chat_id": "chat_4f81b2",
      "state": "open",
      "owner": "human_pending",
      "customer_handle": "urn:mbid:AQAAY7c1",
      "handle_kind": "opaque",
      "origin": "customer",
      "capabilities": [
        "TEXT",
        "LIST",
        "TIME",
        "QUICK"
      ],
      "owner_since": "2026-08-06T14:02:11Z",
      "last_inbound_at": "2026-08-06T14:01:58Z",
      "updated_at": "2026-08-06T14:02:11Z",
      "last_message": {
        "actor": "customer",
        "at": "2026-08-06T14:01:58Z",
        "kind": "text",
        "has_attachment": false,
        "preview": "I need a human agent"
      }
    }
  ],
  "next_cursor": "MjAyNi0wOC0wNlQxNDowMTo1OFogY2hhdF80ZjgxYjI"
}
```
