# Chats

## Create a new chat

`client.chats.create(ChatCreateParamsbody, RequestOptionsoptions?): ChatCreateResponse`

**post** `/v3/chats`

Create a new chat with specified participants and send an initial message.
The initial message is required when creating a chat.

## Message Effects

You can add iMessage effects to make your messages more expressive. Effects are
optional and can be either screen effects (full-screen animations) or bubble effects
(message bubble animations).

**Screen Effects:** `confetti`, `fireworks`, `lasers`, `sparkles`, `celebration`,
`hearts`, `love`, `balloons`, `happy_birthday`, `echo`, `spotlight`

**Bubble Effects:** `slam`, `loud`, `gentle`, `invisible`

Only one effect type can be applied per message.

## Inline Text Decorations (iMessage only)

Use the `text_decorations` array on a text part to apply styling and animations to character ranges.

Each decoration specifies a `range: [start, end)` and exactly one of `style` or `animation`.

**Styles:** `bold`, `italic`, `strikethrough`, `underline`
**Animations:** `big`, `small`, `shake`, `nod`, `explode`, `ripple`, `bloom`, `jitter`

```json
{
  "type": "text",
  "value": "Hello world",
  "text_decorations": [
    { "range": [0, 5], "style": "bold" },
    { "range": [6, 11], "animation": "shake" }
  ]
}
```

**Note:** Style ranges (bold, italic, etc.) may overlap, but animation ranges must not overlap with other animations or styles. Decorations render per recipient, not per message:
in a group with both iMessage and SMS/RCS participants, iMessage recipients see the decorations and SMS/RCS recipients receive the same message as plain text.

## First-Message Link Restriction

To protect sender deliverability, the **first outbound message** of a new chat cannot be a link.
The request is rejected with `400` (error code `1005`) when:

- The message contains a `link` part (explicit rich-preview link), or
- Any `text` part contains a URL.

This rule applies only to `POST /v3/chats`. Follow-up messages on an existing chat
(`POST /v3/chats/{chatId}/messages`) are not subject to this restriction.

## Reusing an Existing Chat

Chats are keyed on the `from` line plus the exact set of `to` handles. Repeating this
request with the same `from` and `to` returns the **existing** chat and sends the message
into it instead of starting a second conversation.

A group chat that has a `display_name` is excluded from that matching. To run several
parallel groups over the same participants, name each one with `PUT /v3/chats/{chatId}`
before creating the next: the following `POST /v3/chats` with the same `to` then returns a
new, separate `chat_id`. Two other cases also produce a new chat instead of reusing one —
the participant set changed (a participant was added or removed), or the `from` line left
the group.

Whenever the response is a new chat, the first-message rules above apply to that request:
no link in the first message, and no `reply_to` or message effect. To send into a chat you
already know, use `POST /v3/chats/{chatId}/messages` with its `chat_id`.

### Parameters

- `body: ChatCreateParams`

  - `from: string`

    Sender phone number in E.164 format. Must be a phone number that the
    authenticated partner has permission to send from.

  - `message: MessageContent`

    Message content container. Groups all message-related fields together,
    separating the "what" (message content) from the "where" (routing fields like from/to).

    A message carries EITHER `parts` — text and attachments, which compose
    into one bubble — or a single `experience` invocation, which renders an
    experience inside Linq's iMessage app. Never both: an app card is the whole message
    (Apple's `MSMessage` cannot coexist with text), so copy and a card are
    two sends, not one.

    - `effect?: MessageEffect`

      iMessage effect to apply to this message (screen or bubble effect)

      - `name?: string`

        Name of the effect. Common values:

        - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
        - Bubble effects: slam, loud, gentle, invisible

      - `type?: "screen" | "bubble"`

        Type of effect

        - `"screen"`

        - `"bubble"`

    - `experience?: Experience`

      Invokes an action on an experience — a third party that renders inside
      Linq's iMessage app. Linq resolves the recipient's connection, mints any
      session the action needs, composes the card and sends it; none of that
      is visible to you.

      Call `GET /v3/experiences/{experience}` for the actions you may invoke
      and the fields each accepts.

      - `action: string`

        Which of its actions, e.g. `attach_card`.

      - `name: string`

        The experience to invoke, e.g. `agentcard` or `agentpay`.

      - `params?: Record<string, unknown>`

        Values for the fields this action exposes. Keys are exactly the
        field names listed for the action — no mapping, no nesting.

        Display copy only, except a `url`-type field — that value sets the
        destination, and must be an absolute `https` URL.

        Some fields are read rather than sent: `agentpay`'s
        `request_payment` takes only a `checkout_url` and resolves the
        amount and reason from that payment request itself, so the card
        cannot state a figure the checkout will not charge.

    - `idempotency_key?: string`

      Optional idempotency key for this message.
      Use this to prevent duplicate sends of the same message. Reusing a key
      whose message was deleted — or was an ephemeral message that has since
      expired — returns 404; the message is never resent.

    - `parts?: Array<TextPart | MediaPart | LinkPart | 2 more>`

      Array of message parts. Each part can be text, media, or link.
      Parts are displayed in order. Text and media can be mixed freely,
      but a `link` part must be the only part in the message.

      **Rich Link Previews:**

      - Use a `link` part to send a URL with a rich preview card
      - A `link` part must be the **only** part in the message
      - To send a URL as plain text (no preview), use a `text` part instead

      **App Clip Payment Cards:**

      - Use an `app_clip` part to send a Linq checkout link as an Apple Pay
        App Clip card (the payment preview with the Open button)
      - An `app_clip` part must be the **only** part in the message
      - iMessage-only: unlike `link`, it never downgrades to SMS/RCS — the
        send fails instead of delivering a bare URL

      **Supported Media:**

      - Images: .jpg, .jpeg, .png, .gif, .heic, .heif, .tif, .tiff, .bmp
      - Videos: .mp4, .mov, .m4v, .mpeg, .mpg, .3gp
      - Audio: .m4a, .mp3, .aac, .caf, .wav, .aiff, .amr
      - Documents: .pdf, .txt, .rtf, .csv, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .pages, .numbers, .key, .epub, .zip, .html, .htm
      - Contact & Calendar: .vcf, .ics

      **Audio:**

      - Audio files (.m4a, .mp3, .aac, .caf, .wav, .aiff, .amr) are fully supported as media parts
      - To send audio as an **iMessage voice memo bubble** (inline playback UI), use the dedicated
        `/v3/chats/{chatId}/voicememo` endpoint instead

      **Validation Rules:**

      - A `link` part must be the **only** part in the message. It cannot be combined
        with text or media parts.
      - An `app_clip` part must be the **only** part in the message. Its `value`
        must be a Linq checkout link (e.g. from `POST /v3/payment_requests`);
        any other URL is rejected.
      - Consecutive text parts are not allowed. Text parts must be separated by
        media parts. For example, [text, text] is invalid, but [text, media, text] is valid.
      - Maximum of **100 parts** total.
      - Media parts using a public `url` (downloaded by the server on send) are
        capped at **40**. Parts using `attachment_id` or presigned URLs
        are exempt from this sub-limit. For bulk media sends exceeding 40 files,
        pre-upload via `POST /v3/attachments` and reference by `attachment_id` or `download_url`.

      - `TextPart`

        - `type: "text"`

          Indicates this is a text message part

          - `"text"`

        - `value: string`

          The text content of the message. This value is sent as-is with no parsing or transformation — Markdown syntax will be delivered as plain text. Use `text_decorations` to apply inline formatting and animations (iMessage only).

        - `mention?: string`

          Mention a chat participant. Group chats only — sending a mention to a direct
          chat is rejected with `409` / `2023`. The chat's service is not a constraint:
          a mention is accepted in any group, including one with SMS/RCS participants.

          Set to their handle — E.164 phone number or Apple ID email. `value` is the
          display text; use the bare name (`"Juan"`, not `"@Juan"`). By default the entire
          `value` renders as the mention; use `mention_range` to highlight only part of it.

          Rendering is per recipient, not per message. iMessage recipients see the mention
          highlighted and are notified even if they have muted the chat. SMS and RCS
          recipients receive the same message as plain text — no highlight, and no mute
          override. One send, two experiences.

        - `mention_range?: Array<number>`

          Optional character range `[start, end)` in `value` that renders as the `mention`
          highlight (e.g. just the name in `"Hey Kevin, can you look at this?"`). Requires
          `mention`. Without it, the entire `value` is highlighted. `start` is inclusive,
          `end` is exclusive.
          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          Applies to iMessage recipients only, matching `mention` — SMS and RCS recipients
          receive the text with no highlight.

        - `text_decorations?: Array<TextDecoration>`

          Optional array of text decorations applied to character ranges in the `value` field (iMessage only).

          Each decoration specifies a character range `[start, end)` and exactly one of `style` or `animation`.

          **Styles:** `bold`, `italic`, `strikethrough`, `underline`
          **Animations:** `big`, `small`, `shake`, `nod`, `explode`, `ripple`, `bloom`, `jitter`

          Style ranges may overlap (e.g. bold + italic on the same text), but animation ranges must not overlap with other animations or styles.

          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          **Note:** decorations render per recipient, not per message. In a group containing
          both iMessage and SMS/RCS participants, iMessage recipients see the decorations and
          SMS/RCS recipients receive the same message as plain text.

          - `range: Array<number>`

            Character range `[start, end)` in the `value` string where the decoration applies.
            `start` is inclusive, `end` is exclusive.
            *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `animation?: "big" | "small" | "shake" | 5 more`

            Animated text effect to apply. Mutually exclusive with `style`.

            - `"big"`

            - `"small"`

            - `"shake"`

            - `"nod"`

            - `"explode"`

            - `"ripple"`

            - `"bloom"`

            - `"jitter"`

          - `style?: "bold" | "italic" | "strikethrough" | "underline"`

            Text style to apply. Mutually exclusive with `animation`.

            - `"bold"`

            - `"italic"`

            - `"strikethrough"`

            - `"underline"`

      - `MediaPart`

        - `type: "media"`

          Indicates this is a media attachment part

          - `"media"`

        - `attachment_id?: string`

          Reference to a file pre-uploaded via `POST /v3/attachments` (optional).
          The file is already stored, so sends using this ID skip the download step —
          useful when sending the same file to many recipients.

          Either `url` or `attachment_id` must be provided, but not both.

        - `sticker?: boolean`

          Send this image as a **sticker** rather than a photo. The recipient can peel it off
          and place it on any message in the conversation, and it renders without a bubble.

          An opaque photo is cut out automatically — the subject is lifted from its background,
          the same way "Add Sticker" does on iOS. An image that already has transparency is
          sent as-is. If no subject can be found, the image sends as an ordinary photo.

          **iMessage only.** On SMS/RCS the flag is ignored and the image sends as a photo.

          Stickers can be combined with a `text` part in the same message; the text arrives as
          its own bubble. To place a sticker *onto* an existing message instead, use
          `POST /v3/messages/{messageId}/reactions` with `type: "sticker"`.

        - `url?: string`

          Any publicly accessible HTTPS URL to the media file. The server downloads and
          sends the file automatically — no pre-upload step required.

          **Size limit:** 10MB maximum for URL-based downloads. For larger files (up to 100MB),
          use the pre-upload flow: `POST /v3/attachments` to get a presigned URL, upload directly,
          then reference by `attachment_id`.

          **Requirements:**

          - URL must use HTTPS
          - File content must be a supported format (the server validates the actual file content)

          **Supported formats:**

          - Images: .jpg, .jpeg, .png, .gif, .heic, .heif, .tif, .tiff, .bmp
          - Videos: .mp4, .mov, .m4v, .mpeg, .mpg, .3gp
          - Audio: .m4a, .mp3, .aac, .caf, .wav, .aiff, .amr
          - Documents: .pdf, .txt, .rtf, .csv, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .pages, .numbers, .key, .epub, .zip, .html, .htm
          - Contact & Calendar: .vcf, .ics

          **Tip:** Audio sent here appears as a regular file attachment. To send audio as an
          iMessage voice memo bubble (with inline playback), use `/v3/chats/{chatId}/voicememo`.
          For repeated sends of the same file, use `attachment_id` to avoid redundant downloads.

          Either `url` or `attachment_id` must be provided, but not both.

      - `LinkPart`

        - `type: "link"`

          Indicates this is a rich link preview part

          - `"link"`

        - `value: string`

          URL to send with a rich link preview. The recipient will see an inline card
          with the page's title, description, and preview image (when available).

          A `link` part must be the **only** part in the message. To send a URL as plain
          text (no preview card), use a `text` part instead.

      - `IMessageAppPart`

        An iMessage app card, backed by a Messages app extension. iMessage only —
        an `imessage_app` part must be the **only** part in the message and is never delivered over
        SMS/RCS. See the IMessageAppServiceUnsupported (2018) and RecipientUnsupportedMessageType
        (4005) error codes.

        - `app: App`

          Identifies the iMessage app (Messages app extension) that backs the card.

          - `bundle_id: string`

            Bundle identifier of the Messages app extension. Must not contain `:`.

          - `name: string`

            Display name of the app, shown by Messages' fallback UI.

          - `team_id: string`

            The app's 10-character uppercase alphanumeric team identifier.

          - `app_store_id?: number`

            The owning app's App Store id (optional). When set, recipients without the iMessage app
            installed see a "Get the app" affordance.

        - `layout: Layout`

          Visible layout of the card. At least one of
          `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
          set, otherwise the card renders as an empty bubble.

          `image_url` displays a preview image at the top of the card. The image renders on the
          recipient's card whether or not they have your app installed. The small icon beside the
          caption is the app's own icon and is not settable here.

          `* Note - requires a trusted chat w/ inbound activity`

          `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
          beneath it). They only appear when `image_url` is set — without an image there is nothing to
          overlay — so setting either without `image_url` is rejected.

          - `caption?: string`

            Primary label, top-left and bold.

          - `image_subtitle?: string`

            Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

          - `image_title?: string`

            Bold text overlaid on the card image. Requires `image_url` (rejected without it).

          - `image_url?: string`

            URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

          - `subcaption?: string`

            Secondary label, below `caption` on the left.

          - `trailing_caption?: string`

            Label shown top-right.

          - `trailing_subcaption?: string`

            Label shown below `trailing_caption`, on the right.

        - `type: "imessage_app"`

          Indicates this is an iMessage app card part.

          - `"imessage_app"`

        - `fallback_text?: string`

          Text shown on surfaces that cannot render the card (notifications, lock screen). Defaults
          to the caption when omitted.

        - `interactive?: boolean`

          Whether the card renders as your app's interactive balloon for recipients who have your
          iMessage app installed. `true` (default) lets your installed extension draw its live,
          interactive view for those recipients; everyone else sees the static card built from
          `layout`. `false` always shows the static `layout` card, even to recipients who have the
          app installed. Recipients without your app always see the static card regardless of this
          flag.

        - `url?: string`

          URL the recipient's app opens when they tap the card. Either an absolute `https://` URL
          (capped at 2048 characters) or a `data:` URL carrying inline app state, e.g. a game's
          encoded state (capped at 16384 characters).

      - `AppClipPart`

        Sends a **registered App Clip** — not only Linq's Apple Pay checkout, but any
        partner's own App Clip. `caption` is optional.

        An `app_clip` part must be the **only** part in the message.

        **iMessage only**, and it never downgrades. A `service_preference` of
        `sms` or `rcs` is rejected (`AppClipServiceUnsupported`, 2028). A
        recipient who can't receive it fails the send rather than being sent a
        plain link in its place.

        - `type: "app_clip"`

          Indicates this is an App Clip card

          - `"app_clip"`

        - `value: string`

          An https link whose page is a registered App Clip — Linq's checkout link
          (e.g. the `checkout_url` from `POST /v3/payment_requests`) or a partner's
          own App Clip URL. A URL that doesn't resolve to a sendable App Clip page is
          rejected.

        - `caption?: string`

          Optional caption for the card's **Open** button row. Omit it and the
          card uses the App Clip's own default (`Tap open`). Set it to override
          that with your own short call to action.

    - `preferred_service?: ServiceType`

      Messaging service type

      - `"iMessage"`

      - `"SMS"`

      - `"RCS"`

    - `reply_to?: ReplyTo`

      Reply to another message to create a threaded conversation

      - `message_id: string`

        The ID of the message to reply to

      - `part_index?: number`

        The specific message part to reply to (0-based index).
        Defaults to 0 (first part) if not provided.
        Use this when replying to a specific part of a multipart message.

  - `to: Array<string>`

    Array of recipient handles (phone numbers in E.164 format or email addresses).
    For individual chats, provide one recipient. For group chats, provide multiple.

  - `override_optout?: boolean`

    Send even though the recipient asked you to stop (`403`, error code
    `2024`). Applies to this request only: the opt-out stays in place, so
    the next send without this flag is rejected again. Every override is
    recorded against your API key.

### Returns

- `ChatCreateResponse`

  Response for creating a new chat with an initial message

  - `chat: Chat`

    - `id: string`

      Unique identifier for the created chat (UUID)

    - `display_name: string | null`

      Display name for the chat. Defaults to a comma-separated list of recipient handles. Can be updated for group chats.

    - `handles: Array<ChatHandle>`

      List of participants in the chat. Always contains at least two handles (your phone number and the other participant).

      - `id: string`

        Unique identifier for this handle

      - `handle: string`

        Phone number (E.164) or email address of the participant

      - `joined_at: string`

        When this participant joined the chat

      - `service: ServiceType`

        Messaging service type

        - `"iMessage"`

        - `"SMS"`

        - `"RCS"`

      - `is_me?: boolean | null`

        Whether this handle belongs to the sender (your phone number)

      - `left_at?: string | null`

        When they left (if applicable)

      - `status?: "active" | "left" | "removed" | null`

        Participant status

        - `"active"`

        - `"left"`

        - `"removed"`

    - `health_status: HealthStatus`

      **[BETA]** Current health for a chat. Always present — chats start at `HEALTHY` and may shift based on engagement and delivery signals on the conversation. Many `AT_RISK` or `CRITICAL` chats on a single line increase the risk of line flagging.

      Switch on `status` to surface chat and line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. To gate a send, act on the response rather than the status: a `403` is the authoritative answer.

      See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each status means and how to react.

      - `doc_url: string`

        Deep-link to the relevant section of the Chat Health guide for this status.

      - `status: "HEALTHY" | "AT_RISK" | "CRITICAL" | "OPTED_OUT"`

        Current health bucket for the chat. See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section.

        `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`, `END`, or `QUIT`.
        The keyword must be the whole trimmed message, never part of a longer one: `STOP` counts, `please stop`
        does not. Most keywords must match exactly, including case. `OPT OUT` is the exception — it matches in any
        casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all count. It clears as
        soon as they reply again: any later message from them that is not itself an opt-out keyword opts them back
        in immediately — a reply in any conversation with you counts, the same way the block does.

        `OPTED_OUT` marks only the conversation the keyword arrived in. The block below is wider than the mark, so
        a conversation still reading `HEALTHY` can be blocked as well — gate on the `403`, not on the status.
        Group threads are never marked and are never blocked.

        Linq enforces this: while a recipient is opted out, every send to them is rejected with `403` (error code
        `2024`) before the message is queued, across every chat and every line on your account. Nothing is
        delivered, including a final courtesy message — to send one, set `override_optout: true` on that single
        request.

        - `"HEALTHY"`

        - `"AT_RISK"`

        - `"CRITICAL"`

        - `"OPTED_OUT"`

      - `updated_at: string`

        When this status last changed.

    - `is_group: boolean`

      Whether this is a group chat

    - `message: SentMessage`

      A message that was sent (used in CreateChat and SendMessage responses)

      - `id: string`

        Message identifier (UUID)

      - `created_at: string`

        When the message was created

      - `delivery_status: "pending" | "queued" | "sent" | 4 more`

        Current delivery status of a message

        - `"pending"`

        - `"queued"`

        - `"sent"`

        - `"delivered"`

        - `"received"`

        - `"read"`

        - `"failed"`

      - `is_read: boolean`

        DEPRECATED: Use `delivery_status == "read"` instead. Whether the message has been read.

      - `parts: Array<TextPartResponse | MediaPartResponse | LinkPartResponse | 2 more>`

        Message parts in order (text, media, and link)

        - `TextPartResponse`

          A text message part

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

              - `id: string`

                Unique identifier for this handle

              - `handle: string`

                Phone number (E.164) or email address of the participant

              - `joined_at: string`

                When this participant joined the chat

              - `service: ServiceType`

                Messaging service type

              - `is_me?: boolean | null`

                Whether this handle belongs to the sender (your phone number)

              - `left_at?: string | null`

                When they left (if applicable)

              - `status?: "active" | "left" | "removed" | null`

                Participant status

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

              - `"love"`

              - `"like"`

              - `"dislike"`

              - `"laugh"`

              - `"emphasize"`

              - `"question"`

              - `"custom"`

              - `"sticker"`

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

              - `file_name?: string`

                Filename of the sticker

              - `height?: number`

                Sticker image height in pixels

              - `mime_type?: string`

                MIME type of the sticker image

              - `url?: string`

                Presigned URL for downloading the sticker image (expires in 1 hour).

              - `width?: number`

                Sticker image width in pixels

          - `type: "text"`

            Indicates this is a text message part

            - `"text"`

          - `value: string`

            The text content

          - `mention?: string | null`

            DEPRECATED: Use `mentions` instead. Handle (E.164 phone number or Apple ID email)
            of the **first** mention on this part. A part may carry several mentions; this
            field shows only the first in `value` order, so it cannot be used to determine
            whether a given participant was mentioned. `null` when the part carries no mention.

          - `mention_range?: Array<number> | null`

            DEPRECATED: Use `mentions[].range` instead. Character range `[start, end)` in
            `value` highlighted as the **first** mention only. `null` when the range was
            omitted (the whole `value` is highlighted) or the part carries no mention.
            *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `mentions?: Array<Mention> | null`

            Every mention on this part, in the order they appear in `value`. `null` when the
            part carries no mention. A part can carry several mentions of different people —
            check `is_me` to tell whether this line was one of them.

            Only iMessage carries mentions. On a received message this is populated when the
            sender was on iMessage; SMS and RCS have no way to mark a mention, so a message
            from an SMS or RCS participant arrives as plain text with `mentions` null, even in
            a group where other participants are on iMessage.

            - `handle: string`

              Address of the mentioned participant, exactly as the device recorded it — an E.164
              phone number or an email address.

            - `is_me: boolean`

              Whether the mentioned participant is this line.

            - `range: Array<number>`

              Character range `[start, end)` in `value` highlighted as this mention.
              *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `text_decorations?: Array<TextDecoration> | null`

            Text decorations applied to character ranges in the value

            - `range: Array<number>`

              Character range `[start, end)` in the `value` string where the decoration applies.
              `start` is inclusive, `end` is exclusive.
              *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

            - `animation?: "big" | "small" | "shake" | 5 more`

              Animated text effect to apply. Mutually exclusive with `style`.

              - `"big"`

              - `"small"`

              - `"shake"`

              - `"nod"`

              - `"explode"`

              - `"ripple"`

              - `"bloom"`

              - `"jitter"`

            - `style?: "bold" | "italic" | "strikethrough" | "underline"`

              Text style to apply. Mutually exclusive with `animation`.

              - `"bold"`

              - `"italic"`

              - `"strikethrough"`

              - `"underline"`

        - `MediaPartResponse`

          A media attachment part

          - `id: string`

            Unique attachment identifier

          - `filename: string`

            Original filename

          - `mime_type: string`

            MIME type of the file

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `size_bytes: number`

            File size in bytes

          - `type: "media"`

            Indicates this is a media attachment part

            - `"media"`

          - `url: string`

            Presigned URL for downloading the attachment (expires in 1 hour).

        - `LinkPartResponse`

          A rich link preview part

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `type: "link"`

            Indicates this is a rich link preview part

            - `"link"`

          - `value: string`

            The URL

        - `IMessageAppPartResponse`

          An iMessage app card part.

          - `app: App`

            Identifies the iMessage app (Messages app extension) that backs the card.

            - `bundle_id: string`

              Bundle identifier of the Messages app extension. Must not contain `:`.

            - `name: string`

              Display name of the app, shown by Messages' fallback UI.

            - `team_id: string`

              The app's 10-character uppercase alphanumeric team identifier.

            - `app_store_id?: number`

              The owning app's App Store id (optional). When set, recipients without the iMessage app
              installed see a "Get the app" affordance.

          - `layout: Layout`

            Visible layout of the card. At least one of
            `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
            set, otherwise the card renders as an empty bubble.

            `image_url` displays a preview image at the top of the card. The image renders on the
            recipient's card whether or not they have your app installed. The small icon beside the
            caption is the app's own icon and is not settable here.

            `* Note - requires a trusted chat w/ inbound activity`

            `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
            beneath it). They only appear when `image_url` is set — without an image there is nothing to
            overlay — so setting either without `image_url` is rejected.

            - `caption?: string`

              Primary label, top-left and bold.

            - `image_subtitle?: string`

              Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

            - `image_title?: string`

              Bold text overlaid on the card image. Requires `image_url` (rejected without it).

            - `image_url?: string`

              URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

            - `subcaption?: string`

              Secondary label, below `caption` on the left.

            - `trailing_caption?: string`

              Label shown top-right.

            - `trailing_subcaption?: string`

              Label shown below `trailing_caption`, on the right.

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `type: "imessage_app"`

            Indicates this is an iMessage app card part.

            - `"imessage_app"`

          - `url: string`

            The URL delivered to the iMessage app on tap.

          - `fallback_text?: string | null`

            Fallback text for surfaces that cannot render the card.

        - `AppClipPartResponse`

          An App Clip card part

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `type: "app_clip"`

            Indicates this is an App Clip card part

            - `"app_clip"`

          - `value: string`

            The App Clip link the card opens

          - `description?: string`

            The card's summary line, composed by Linq from the App Clip page

          - `image_url?: string`

            The card's preview image

          - `title?: string`

            The card's headline, composed by Linq from the App Clip page

      - `sent_at: string | null`

        When the message was actually sent (null if still queued)

      - `delivered_at?: string | null`

        When the message was delivered

      - `effect?: MessageEffect | null`

        iMessage effect applied to a message (screen or bubble effect)

        - `name?: string`

          Name of the effect. Common values:

          - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
          - Bubble effects: slam, loud, gentle, invisible

        - `type?: "screen" | "bubble"`

          Type of effect

          - `"screen"`

          - `"bubble"`

      - `from_handle?: ChatHandle | null`

        The sender of this message as a full handle object

      - `preferred_service?: ServiceType | null`

        Messaging service type

      - `reply_to?: ReplyTo | null`

        Indicates this message is a threaded reply to another message

        - `message_id: string`

          The ID of the message to reply to

        - `part_index?: number`

          The specific message part to reply to (0-based index).
          Defaults to 0 (first part) if not provided.
          Use this when replying to a specific part of a multipart message.

      - `service?: ServiceType | null`

        Messaging service type

    - `service: ServiceType`

      Messaging service type

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const chat = await client.chats.create({
  from: '+12052535597',
  message: { parts: [{ type: 'text', value: 'Hello! How can I help you today?' }] },
  to: ['+12052532136'],
});

console.log(chat.chat);
```

#### Response

```json
{
  "chat": {
    "id": "94c6bf33-31d9-40e3-a0e9-f94250ecedb9",
    "display_name": "+14155551234, +14155559876",
    "handles": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440010",
        "handle": "+14155551234",
        "joined_at": "2025-05-21T15:30:00.000Z",
        "service": "iMessage",
        "is_me": true,
        "left_at": "2019-12-27T18:11:19.117Z",
        "status": "active"
      },
      {
        "id": "550e8400-e29b-41d4-a716-446655440011",
        "handle": "+14155559876",
        "joined_at": "2025-05-21T15:30:00.000Z",
        "service": "iMessage",
        "is_me": false,
        "left_at": "2019-12-27T18:11:19.117Z",
        "status": "active"
      }
    ],
    "health_status": {
      "doc_url": "https://docs.linqapp.com/channel/imessage/guides/chats/chat-health#at-risk",
      "status": "AT_RISK",
      "updated_at": "2026-05-01T18:28:25Z"
    },
    "is_group": false,
    "message": {
      "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
      "created_at": "2025-10-23T13:07:55.019-05:00",
      "delivery_status": "pending",
      "is_read": false,
      "parts": [
        {
          "reactions": [
            {
              "handle": {
                "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
                "handle": "+15551234567",
                "joined_at": "2025-05-21T15:30:00.000-05:00",
                "service": "iMessage",
                "is_me": false,
                "left_at": "2019-12-27T18:11:19.117Z",
                "status": "active"
              },
              "is_me": false,
              "type": "love",
              "id": "9f8b1c2d-3e4f-5061-7283-94a5b6c7d8e9",
              "custom_emoji": null,
              "sticker": {
                "file_name": "sticker.png",
                "height": 420,
                "mime_type": "image/png",
                "url": "https://cdn.linqapp.com/attachments/a1b2c3d4/sticker.png?signature=...",
                "width": 420
              }
            }
          ],
          "type": "text",
          "value": "Hello!",
          "mention": "+14155551234",
          "mention_range": [
            4,
            9
          ],
          "mentions": [
            {
              "handle": "+14155550123",
              "is_me": true,
              "range": [
                4,
                9
              ]
            }
          ],
          "text_decorations": [
            {
              "range": [
                0,
                5
              ],
              "animation": "shake",
              "style": "bold"
            }
          ]
        }
      ],
      "sent_at": null,
      "delivered_at": null,
      "effect": {
        "name": "confetti",
        "type": "screen"
      },
      "from_handle": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "handle": "+15551234567",
        "joined_at": "2025-05-21T15:30:00.000-05:00",
        "service": "iMessage",
        "is_me": false,
        "left_at": "2019-12-27T18:11:19.117Z",
        "status": "active"
      },
      "preferred_service": "iMessage",
      "reply_to": {
        "message_id": "550e8400-e29b-41d4-a716-446655440000",
        "part_index": 0
      },
      "service": "iMessage"
    },
    "service": "iMessage"
  }
}
```

## List all chats

`client.chats.listChats(ChatListChatsParamsquery?, RequestOptionsoptions?): ListChatsPagination<Chat>`

**get** `/v3/chats`

Retrieves a paginated list of chats for the authenticated partner.

**Filtering:**

- If `from` is provided, returns chats for that specific phone number
- If `from` is omitted, returns chats across all phone numbers owned by the partner
- If `to` is provided, only returns chats where the specified handle is a participant

**Pagination:**

- Use `limit` to control page size (default: 20, max: 100)
- The response includes `next_cursor` for fetching the next page
- When `next_cursor` is `null`, there are no more results to fetch
- Pass the `next_cursor` value as the `cursor` parameter for the next request

**Example pagination flow:**

1. First request: `GET /v3/chats?from=%2B12223334444&limit=20`
1. Response includes `next_cursor: "20"` (more results exist)
1. Next request: `GET /v3/chats?from=%2B12223334444&limit=20&cursor=20`
1. Response includes `next_cursor: null` (no more results)

### Parameters

- `query: ChatListChatsParams`

  - `cursor?: string`

    Pagination cursor from the previous response's `next_cursor` field.
    Omit this parameter for the first page of results.

  - `from?: string`

    Phone number to filter chats by. Returns chats made from this phone number.
    Must be in E.164 format (e.g., `+13343284472`). The `+` is automatically URL-encoded by HTTP clients.
    If omitted, returns chats across all phone numbers owned by the partner.

  - `limit?: number`

    Maximum number of chats to return per page

  - `to?: string`

    Filter chats by a participant handle. Only returns chats where this handle is a participant.
    Can be an E.164 phone number (e.g., `+13343284472`) or an email address (e.g., `user@example.com`).
    For phone numbers, the `+` is automatically URL-encoded by HTTP clients.

### Returns

- `Chat`

  - `id: string`

    Unique identifier for the chat

  - `created_at: string`

    When the chat was created

  - `display_name: string | null`

    Display name for the chat. Defaults to a comma-separated list of recipient handles. Can be updated for group chats.

  - `handles: Array<ChatHandle>`

    List of chat participants with full handle details. Always contains at least two handles (your phone number and the other participant).

    - `id: string`

      Unique identifier for this handle

    - `handle: string`

      Phone number (E.164) or email address of the participant

    - `joined_at: string`

      When this participant joined the chat

    - `service: ServiceType`

      Messaging service type

      - `"iMessage"`

      - `"SMS"`

      - `"RCS"`

    - `is_me?: boolean | null`

      Whether this handle belongs to the sender (your phone number)

    - `left_at?: string | null`

      When they left (if applicable)

    - `status?: "active" | "left" | "removed" | null`

      Participant status

      - `"active"`

      - `"left"`

      - `"removed"`

  - `health_status: HealthStatus`

    **[BETA]** Current health for a chat. Always present — chats start at `HEALTHY` and may shift based on engagement and delivery signals on the conversation. Many `AT_RISK` or `CRITICAL` chats on a single line increase the risk of line flagging.

    Switch on `status` to surface chat and line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. To gate a send, act on the response rather than the status: a `403` is the authoritative answer.

    See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each status means and how to react.

    - `doc_url: string`

      Deep-link to the relevant section of the Chat Health guide for this status.

    - `status: "HEALTHY" | "AT_RISK" | "CRITICAL" | "OPTED_OUT"`

      Current health bucket for the chat. See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section.

      `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`, `END`, or `QUIT`.
      The keyword must be the whole trimmed message, never part of a longer one: `STOP` counts, `please stop`
      does not. Most keywords must match exactly, including case. `OPT OUT` is the exception — it matches in any
      casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all count. It clears as
      soon as they reply again: any later message from them that is not itself an opt-out keyword opts them back
      in immediately — a reply in any conversation with you counts, the same way the block does.

      `OPTED_OUT` marks only the conversation the keyword arrived in. The block below is wider than the mark, so
      a conversation still reading `HEALTHY` can be blocked as well — gate on the `403`, not on the status.
      Group threads are never marked and are never blocked.

      Linq enforces this: while a recipient is opted out, every send to them is rejected with `403` (error code
      `2024`) before the message is queued, across every chat and every line on your account. Nothing is
      delivered, including a final courtesy message — to send one, set `override_optout: true` on that single
      request.

      - `"HEALTHY"`

      - `"AT_RISK"`

      - `"CRITICAL"`

      - `"OPTED_OUT"`

    - `updated_at: string`

      When this status last changed.

  - `is_archived: boolean`

    **DEPRECATED:** This field is deprecated and will be removed in a future API version.

  - `is_group: boolean`

    Whether this is a group chat

  - `updated_at: string`

    When the chat was last updated

  - `group_chat_icon?: string | null`

    URL of the group chat icon. Only set for group chats that have an icon; `null` otherwise.

  - `service?: ServiceType | null`

    Messaging service type

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

// Automatically fetches more pages as needed.
for await (const chat of client.chats.listChats()) {
  console.log(chat.id);
}
```

#### Response

```json
{
  "chats": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "created_at": "2024-01-15T10:30:00Z",
      "display_name": "+14155551234, +14155559876",
      "handles": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440010",
          "handle": "+14155551234",
          "joined_at": "2025-05-21T15:30:00.000Z",
          "service": "iMessage",
          "is_me": true,
          "left_at": "2019-12-27T18:11:19.117Z",
          "status": "active"
        },
        {
          "id": "550e8400-e29b-41d4-a716-446655440011",
          "handle": "+14155559876",
          "joined_at": "2025-05-21T15:30:00.000Z",
          "service": "iMessage",
          "is_me": false,
          "left_at": "2019-12-27T18:11:19.117Z",
          "status": "active"
        }
      ],
      "health_status": {
        "doc_url": "https://docs.linqapp.com/channel/imessage/guides/chats/chat-health#at-risk",
        "status": "AT_RISK",
        "updated_at": "2026-05-01T18:28:25Z"
      },
      "is_archived": true,
      "is_group": true,
      "updated_at": "2024-01-15T10:30:00Z",
      "group_chat_icon": "https://example.com/group-icon.png",
      "service": "iMessage"
    }
  ],
  "next_cursor": "next_cursor"
}
```

## Get a chat by ID

`client.chats.retrieve(stringchatID, RequestOptionsoptions?): Chat`

**get** `/v3/chats/{chatId}`

Retrieve a chat by its unique identifier.

### Parameters

- `chatID: string`

### Returns

- `Chat`

  - `id: string`

    Unique identifier for the chat

  - `created_at: string`

    When the chat was created

  - `display_name: string | null`

    Display name for the chat. Defaults to a comma-separated list of recipient handles. Can be updated for group chats.

  - `handles: Array<ChatHandle>`

    List of chat participants with full handle details. Always contains at least two handles (your phone number and the other participant).

    - `id: string`

      Unique identifier for this handle

    - `handle: string`

      Phone number (E.164) or email address of the participant

    - `joined_at: string`

      When this participant joined the chat

    - `service: ServiceType`

      Messaging service type

      - `"iMessage"`

      - `"SMS"`

      - `"RCS"`

    - `is_me?: boolean | null`

      Whether this handle belongs to the sender (your phone number)

    - `left_at?: string | null`

      When they left (if applicable)

    - `status?: "active" | "left" | "removed" | null`

      Participant status

      - `"active"`

      - `"left"`

      - `"removed"`

  - `health_status: HealthStatus`

    **[BETA]** Current health for a chat. Always present — chats start at `HEALTHY` and may shift based on engagement and delivery signals on the conversation. Many `AT_RISK` or `CRITICAL` chats on a single line increase the risk of line flagging.

    Switch on `status` to surface chat and line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. To gate a send, act on the response rather than the status: a `403` is the authoritative answer.

    See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each status means and how to react.

    - `doc_url: string`

      Deep-link to the relevant section of the Chat Health guide for this status.

    - `status: "HEALTHY" | "AT_RISK" | "CRITICAL" | "OPTED_OUT"`

      Current health bucket for the chat. See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section.

      `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`, `END`, or `QUIT`.
      The keyword must be the whole trimmed message, never part of a longer one: `STOP` counts, `please stop`
      does not. Most keywords must match exactly, including case. `OPT OUT` is the exception — it matches in any
      casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all count. It clears as
      soon as they reply again: any later message from them that is not itself an opt-out keyword opts them back
      in immediately — a reply in any conversation with you counts, the same way the block does.

      `OPTED_OUT` marks only the conversation the keyword arrived in. The block below is wider than the mark, so
      a conversation still reading `HEALTHY` can be blocked as well — gate on the `403`, not on the status.
      Group threads are never marked and are never blocked.

      Linq enforces this: while a recipient is opted out, every send to them is rejected with `403` (error code
      `2024`) before the message is queued, across every chat and every line on your account. Nothing is
      delivered, including a final courtesy message — to send one, set `override_optout: true` on that single
      request.

      - `"HEALTHY"`

      - `"AT_RISK"`

      - `"CRITICAL"`

      - `"OPTED_OUT"`

    - `updated_at: string`

      When this status last changed.

  - `is_archived: boolean`

    **DEPRECATED:** This field is deprecated and will be removed in a future API version.

  - `is_group: boolean`

    Whether this is a group chat

  - `updated_at: string`

    When the chat was last updated

  - `group_chat_icon?: string | null`

    URL of the group chat icon. Only set for group chats that have an icon; `null` otherwise.

  - `service?: ServiceType | null`

    Messaging service type

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const chat = await client.chats.retrieve('550e8400-e29b-41d4-a716-446655440000');

console.log(chat.id);
```

#### Response

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "created_at": "2024-01-15T10:30:00Z",
  "display_name": "+14155551234, +14155559876",
  "handles": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440010",
      "handle": "+14155551234",
      "joined_at": "2025-05-21T15:30:00.000Z",
      "service": "iMessage",
      "is_me": true,
      "left_at": "2019-12-27T18:11:19.117Z",
      "status": "active"
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440011",
      "handle": "+14155559876",
      "joined_at": "2025-05-21T15:30:00.000Z",
      "service": "iMessage",
      "is_me": false,
      "left_at": "2019-12-27T18:11:19.117Z",
      "status": "active"
    }
  ],
  "health_status": {
    "doc_url": "https://docs.linqapp.com/channel/imessage/guides/chats/chat-health#at-risk",
    "status": "AT_RISK",
    "updated_at": "2026-05-01T18:28:25Z"
  },
  "is_archived": true,
  "is_group": true,
  "updated_at": "2024-01-15T10:30:00Z",
  "group_chat_icon": "https://example.com/group-icon.png",
  "service": "iMessage"
}
```

## Update a chat

`client.chats.update(stringchatID, ChatUpdateParamsbody, RequestOptionsoptions?): ChatUpdateResponse`

**put** `/v3/chats/{chatId}`

Update chat properties such as display name and group chat icon.

Listen for `chat.group_name_updated`, `chat.group_icon_updated`,
`chat.group_name_update_failed`, or `chat.group_icon_update_failed`
webhook events to confirm the outcome.

### Parameters

- `chatID: string`

- `body: ChatUpdateParams`

  - `display_name?: string`

    New display name for the chat (group chats only)

  - `group_chat_icon?: string`

    URL of an image to set as the group chat icon (group chats only)

### Returns

- `ChatUpdateResponse`

  - `chat_id?: string`

  - `status?: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const chat = await client.chats.update('550e8400-e29b-41d4-a716-446655440000', {
  display_name: 'Team Discussion',
});

console.log(chat.chat_id);
```

#### Response

```json
{
  "chat_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending"
}
```

## Mark chat as read

`client.chats.markAsRead(stringchatID, RequestOptionsoptions?): void`

**post** `/v3/chats/{chatId}/read`

Mark all messages in a chat as read.

### Parameters

- `chatID: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

await client.chats.markAsRead('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');
```

#### Response

```json
{
  "error": {
    "status": 401,
    "code": 2004,
    "message": "Unauthorized - missing or invalid authentication token",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/2xxx/2004/"
  },
  "success": false
}
```

## Leave a group chat

`client.chats.leaveChat(stringchatID, RequestOptionsoptions?): ChatLeaveChatResponse`

**post** `/v3/chats/{chatId}/leave`

Removes your phone number from a group chat. Once you leave, you will no longer receive messages from the group and all interaction endpoints (send message, typing, mark read, etc.) will return 409.

A `participant.removed` webhook will fire once the leave has been processed.

**Supported**

- iMessage group chats with 4 or more active participants (including yourself)

**Not supported**

- DM (1-on-1) chats — use the chat directly to continue the conversation

### Parameters

- `chatID: string`

### Returns

- `ChatLeaveChatResponse`

  - `message?: string`

  - `status?: string`

  - `trace_id?: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const response = await client.chats.leaveChat('550e8400-e29b-41d4-a716-446655440000');

console.log(response.trace_id);
```

#### Response

```json
{
  "message": "Leave group chat queued",
  "status": "accepted",
  "trace_id": "trace_id"
}
```

## Share your contact card with a chat

`client.chats.shareContactCard(stringchatID, RequestOptionsoptions?): void`

**post** `/v3/chats/{chatId}/share_contact_card`

Share your contact information (Name and Photo Sharing) with a chat.

**Note:** A contact card must be configured before sharing. You can set up your contact card via the [Contact Card API](#tag/Contact-Card) or on the [Linq dashboard](https://dashboard.linqapp.com/contact-cards).

### Parameters

- `chatID: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

await client.chats.shareContactCard('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e');
```

#### Response

```json
{
  "error": {
    "status": 401,
    "code": 2004,
    "message": "Unauthorized - missing or invalid authentication token",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/2xxx/2004/"
  },
  "success": false
}
```

## Send a voice memo to a chat

`client.chats.sendVoicememo(stringchatID, ChatSendVoicememoParamsbody, RequestOptionsoptions?): ChatSendVoicememoResponse`

**post** `/v3/chats/{chatId}/voicememo`

Send an audio file as an **iMessage voice memo bubble** to all participants in a chat.
Voice memos appear with iMessage's native inline playback UI, unlike regular audio
attachments sent via media parts which appear as downloadable files.

**Supported audio formats:**

- MP3 (audio/mpeg)
- M4A (audio/x-m4a, audio/mp4)
- AAC (audio/aac)
- CAF (audio/x-caf) - Core Audio Format
- WAV (audio/wav)
- AIFF (audio/aiff, audio/x-aiff)
- AMR (audio/amr)

### Parameters

- `chatID: string`

- `body: ChatSendVoicememoParams`

  - `attachment_id?: string`

    Reference to a voice memo file pre-uploaded via `POST /v3/attachments`.
    The file is already stored, so sends using this ID skip the download step.

    Either `voice_memo_url` or `attachment_id` must be provided, but not both.

  - `override_optout?: boolean`

    Send even though the recipient asked you to stop (`403`, error code
    `2024`). Applies to this request only: the opt-out stays in place, so
    the next send without this flag is rejected again. Every override is
    recorded against your API key.

  - `voice_memo_url?: string`

    URL of the voice memo audio file. Must be a publicly accessible HTTPS URL.

    Either `voice_memo_url` or `attachment_id` must be provided, but not both.

### Returns

- `ChatSendVoicememoResponse`

  Response for sending a voice memo to a chat

  - `voice_memo: VoiceMemo`

    - `id: string`

      Message identifier

    - `chat: Chat`

      - `id: string`

        Chat identifier

      - `handles: Array<ChatHandle>`

        Chat participants

        - `id: string`

          Unique identifier for this handle

        - `handle: string`

          Phone number (E.164) or email address of the participant

        - `joined_at: string`

          When this participant joined the chat

        - `service: ServiceType`

          Messaging service type

          - `"iMessage"`

          - `"SMS"`

          - `"RCS"`

        - `is_me?: boolean | null`

          Whether this handle belongs to the sender (your phone number)

        - `left_at?: string | null`

          When they left (if applicable)

        - `status?: "active" | "left" | "removed" | null`

          Participant status

          - `"active"`

          - `"left"`

          - `"removed"`

      - `is_active: boolean`

        Whether the chat is active

      - `is_group: boolean`

        Whether this is a group chat

      - `service: ServiceType`

        Messaging service type

    - `created_at: string`

      When the voice memo was created

    - `from: string`

      Sender phone number

    - `status: string`

      Current delivery status

    - `to: Array<string>`

      Recipient handles (phone numbers or email addresses)

    - `voice_memo: VoiceMemo`

      - `id: string`

        Attachment identifier

      - `filename: string`

        Original filename

      - `mime_type: string`

        Audio MIME type

      - `size_bytes: number`

        File size in bytes

      - `url: string`

        CDN URL for downloading the voice memo

      - `duration_ms?: number | null`

        Duration in milliseconds

    - `service?: ServiceType | null`

      Messaging service type

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const response = await client.chats.sendVoicememo('f19ee7b8-8533-4c5c-83ec-4ef8d6d1ddbd', {
  voice_memo_url: 'https://example.com/voice-memo.m4a',
});

console.log(response.voice_memo);
```

#### Response

```json
{
  "voice_memo": {
    "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
    "chat": {
      "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
      "handles": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440000",
          "handle": "+15551234567",
          "joined_at": "2025-05-21T15:30:00.000-05:00",
          "service": "iMessage",
          "is_me": false,
          "left_at": "2019-12-27T18:11:19.117Z",
          "status": "active"
        }
      ],
      "is_active": true,
      "is_group": true,
      "service": "iMessage"
    },
    "created_at": "2019-12-27T18:11:19.117Z",
    "from": "+12052535597",
    "status": "queued",
    "to": [
      "+12052532136"
    ],
    "voice_memo": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "filename": "voice-memo.m4a",
      "mime_type": "audio/x-m4a",
      "size_bytes": 524288,
      "url": "https://cdn.linqapp.com/voice-memos/abc123.m4a",
      "duration_ms": 15000
    },
    "service": "iMessage"
  }
}
```

## Domain Types

### Chat

- `Chat`

  - `id: string`

    Unique identifier for the chat

  - `created_at: string`

    When the chat was created

  - `display_name: string | null`

    Display name for the chat. Defaults to a comma-separated list of recipient handles. Can be updated for group chats.

  - `handles: Array<ChatHandle>`

    List of chat participants with full handle details. Always contains at least two handles (your phone number and the other participant).

    - `id: string`

      Unique identifier for this handle

    - `handle: string`

      Phone number (E.164) or email address of the participant

    - `joined_at: string`

      When this participant joined the chat

    - `service: ServiceType`

      Messaging service type

      - `"iMessage"`

      - `"SMS"`

      - `"RCS"`

    - `is_me?: boolean | null`

      Whether this handle belongs to the sender (your phone number)

    - `left_at?: string | null`

      When they left (if applicable)

    - `status?: "active" | "left" | "removed" | null`

      Participant status

      - `"active"`

      - `"left"`

      - `"removed"`

  - `health_status: HealthStatus`

    **[BETA]** Current health for a chat. Always present — chats start at `HEALTHY` and may shift based on engagement and delivery signals on the conversation. Many `AT_RISK` or `CRITICAL` chats on a single line increase the risk of line flagging.

    Switch on `status` to surface chat and line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. To gate a send, act on the response rather than the status: a `403` is the authoritative answer.

    See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each status means and how to react.

    - `doc_url: string`

      Deep-link to the relevant section of the Chat Health guide for this status.

    - `status: "HEALTHY" | "AT_RISK" | "CRITICAL" | "OPTED_OUT"`

      Current health bucket for the chat. See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section.

      `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`, `END`, or `QUIT`.
      The keyword must be the whole trimmed message, never part of a longer one: `STOP` counts, `please stop`
      does not. Most keywords must match exactly, including case. `OPT OUT` is the exception — it matches in any
      casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all count. It clears as
      soon as they reply again: any later message from them that is not itself an opt-out keyword opts them back
      in immediately — a reply in any conversation with you counts, the same way the block does.

      `OPTED_OUT` marks only the conversation the keyword arrived in. The block below is wider than the mark, so
      a conversation still reading `HEALTHY` can be blocked as well — gate on the `403`, not on the status.
      Group threads are never marked and are never blocked.

      Linq enforces this: while a recipient is opted out, every send to them is rejected with `403` (error code
      `2024`) before the message is queued, across every chat and every line on your account. Nothing is
      delivered, including a final courtesy message — to send one, set `override_optout: true` on that single
      request.

      - `"HEALTHY"`

      - `"AT_RISK"`

      - `"CRITICAL"`

      - `"OPTED_OUT"`

    - `updated_at: string`

      When this status last changed.

  - `is_archived: boolean`

    **DEPRECATED:** This field is deprecated and will be removed in a future API version.

  - `is_group: boolean`

    Whether this is a group chat

  - `updated_at: string`

    When the chat was last updated

  - `group_chat_icon?: string | null`

    URL of the group chat icon. Only set for group chats that have an icon; `null` otherwise.

  - `service?: ServiceType | null`

    Messaging service type

### Link Part

- `LinkPart`

  - `type: "link"`

    Indicates this is a rich link preview part

    - `"link"`

  - `value: string`

    URL to send with a rich link preview. The recipient will see an inline card
    with the page's title, description, and preview image (when available).

    A `link` part must be the **only** part in the message. To send a URL as plain
    text (no preview card), use a `text` part instead.

### Media Part

- `MediaPart`

  - `type: "media"`

    Indicates this is a media attachment part

    - `"media"`

  - `attachment_id?: string`

    Reference to a file pre-uploaded via `POST /v3/attachments` (optional).
    The file is already stored, so sends using this ID skip the download step —
    useful when sending the same file to many recipients.

    Either `url` or `attachment_id` must be provided, but not both.

  - `sticker?: boolean`

    Send this image as a **sticker** rather than a photo. The recipient can peel it off
    and place it on any message in the conversation, and it renders without a bubble.

    An opaque photo is cut out automatically — the subject is lifted from its background,
    the same way "Add Sticker" does on iOS. An image that already has transparency is
    sent as-is. If no subject can be found, the image sends as an ordinary photo.

    **iMessage only.** On SMS/RCS the flag is ignored and the image sends as a photo.

    Stickers can be combined with a `text` part in the same message; the text arrives as
    its own bubble. To place a sticker *onto* an existing message instead, use
    `POST /v3/messages/{messageId}/reactions` with `type: "sticker"`.

  - `url?: string`

    Any publicly accessible HTTPS URL to the media file. The server downloads and
    sends the file automatically — no pre-upload step required.

    **Size limit:** 10MB maximum for URL-based downloads. For larger files (up to 100MB),
    use the pre-upload flow: `POST /v3/attachments` to get a presigned URL, upload directly,
    then reference by `attachment_id`.

    **Requirements:**

    - URL must use HTTPS
    - File content must be a supported format (the server validates the actual file content)

    **Supported formats:**

    - Images: .jpg, .jpeg, .png, .gif, .heic, .heif, .tif, .tiff, .bmp
    - Videos: .mp4, .mov, .m4v, .mpeg, .mpg, .3gp
    - Audio: .m4a, .mp3, .aac, .caf, .wav, .aiff, .amr
    - Documents: .pdf, .txt, .rtf, .csv, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .pages, .numbers, .key, .epub, .zip, .html, .htm
    - Contact & Calendar: .vcf, .ics

    **Tip:** Audio sent here appears as a regular file attachment. To send audio as an
    iMessage voice memo bubble (with inline playback), use `/v3/chats/{chatId}/voicememo`.
    For repeated sends of the same file, use `attachment_id` to avoid redundant downloads.

    Either `url` or `attachment_id` must be provided, but not both.

### Message Content

- `MessageContent`

  Message content container. Groups all message-related fields together,
  separating the "what" (message content) from the "where" (routing fields like from/to).

  A message carries EITHER `parts` — text and attachments, which compose
  into one bubble — or a single `experience` invocation, which renders an
  experience inside Linq's iMessage app. Never both: an app card is the whole message
  (Apple's `MSMessage` cannot coexist with text), so copy and a card are
  two sends, not one.

  - `effect?: MessageEffect`

    iMessage effect to apply to this message (screen or bubble effect)

    - `name?: string`

      Name of the effect. Common values:

      - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
      - Bubble effects: slam, loud, gentle, invisible

    - `type?: "screen" | "bubble"`

      Type of effect

      - `"screen"`

      - `"bubble"`

  - `experience?: Experience`

    Invokes an action on an experience — a third party that renders inside
    Linq's iMessage app. Linq resolves the recipient's connection, mints any
    session the action needs, composes the card and sends it; none of that
    is visible to you.

    Call `GET /v3/experiences/{experience}` for the actions you may invoke
    and the fields each accepts.

    - `action: string`

      Which of its actions, e.g. `attach_card`.

    - `name: string`

      The experience to invoke, e.g. `agentcard` or `agentpay`.

    - `params?: Record<string, unknown>`

      Values for the fields this action exposes. Keys are exactly the
      field names listed for the action — no mapping, no nesting.

      Display copy only, except a `url`-type field — that value sets the
      destination, and must be an absolute `https` URL.

      Some fields are read rather than sent: `agentpay`'s
      `request_payment` takes only a `checkout_url` and resolves the
      amount and reason from that payment request itself, so the card
      cannot state a figure the checkout will not charge.

  - `idempotency_key?: string`

    Optional idempotency key for this message.
    Use this to prevent duplicate sends of the same message. Reusing a key
    whose message was deleted — or was an ephemeral message that has since
    expired — returns 404; the message is never resent.

  - `parts?: Array<TextPart | MediaPart | LinkPart | 2 more>`

    Array of message parts. Each part can be text, media, or link.
    Parts are displayed in order. Text and media can be mixed freely,
    but a `link` part must be the only part in the message.

    **Rich Link Previews:**

    - Use a `link` part to send a URL with a rich preview card
    - A `link` part must be the **only** part in the message
    - To send a URL as plain text (no preview), use a `text` part instead

    **App Clip Payment Cards:**

    - Use an `app_clip` part to send a Linq checkout link as an Apple Pay
      App Clip card (the payment preview with the Open button)
    - An `app_clip` part must be the **only** part in the message
    - iMessage-only: unlike `link`, it never downgrades to SMS/RCS — the
      send fails instead of delivering a bare URL

    **Supported Media:**

    - Images: .jpg, .jpeg, .png, .gif, .heic, .heif, .tif, .tiff, .bmp
    - Videos: .mp4, .mov, .m4v, .mpeg, .mpg, .3gp
    - Audio: .m4a, .mp3, .aac, .caf, .wav, .aiff, .amr
    - Documents: .pdf, .txt, .rtf, .csv, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .pages, .numbers, .key, .epub, .zip, .html, .htm
    - Contact & Calendar: .vcf, .ics

    **Audio:**

    - Audio files (.m4a, .mp3, .aac, .caf, .wav, .aiff, .amr) are fully supported as media parts
    - To send audio as an **iMessage voice memo bubble** (inline playback UI), use the dedicated
      `/v3/chats/{chatId}/voicememo` endpoint instead

    **Validation Rules:**

    - A `link` part must be the **only** part in the message. It cannot be combined
      with text or media parts.
    - An `app_clip` part must be the **only** part in the message. Its `value`
      must be a Linq checkout link (e.g. from `POST /v3/payment_requests`);
      any other URL is rejected.
    - Consecutive text parts are not allowed. Text parts must be separated by
      media parts. For example, [text, text] is invalid, but [text, media, text] is valid.
    - Maximum of **100 parts** total.
    - Media parts using a public `url` (downloaded by the server on send) are
      capped at **40**. Parts using `attachment_id` or presigned URLs
      are exempt from this sub-limit. For bulk media sends exceeding 40 files,
      pre-upload via `POST /v3/attachments` and reference by `attachment_id` or `download_url`.

    - `TextPart`

      - `type: "text"`

        Indicates this is a text message part

        - `"text"`

      - `value: string`

        The text content of the message. This value is sent as-is with no parsing or transformation — Markdown syntax will be delivered as plain text. Use `text_decorations` to apply inline formatting and animations (iMessage only).

      - `mention?: string`

        Mention a chat participant. Group chats only — sending a mention to a direct
        chat is rejected with `409` / `2023`. The chat's service is not a constraint:
        a mention is accepted in any group, including one with SMS/RCS participants.

        Set to their handle — E.164 phone number or Apple ID email. `value` is the
        display text; use the bare name (`"Juan"`, not `"@Juan"`). By default the entire
        `value` renders as the mention; use `mention_range` to highlight only part of it.

        Rendering is per recipient, not per message. iMessage recipients see the mention
        highlighted and are notified even if they have muted the chat. SMS and RCS
        recipients receive the same message as plain text — no highlight, and no mute
        override. One send, two experiences.

      - `mention_range?: Array<number>`

        Optional character range `[start, end)` in `value` that renders as the `mention`
        highlight (e.g. just the name in `"Hey Kevin, can you look at this?"`). Requires
        `mention`. Without it, the entire `value` is highlighted. `start` is inclusive,
        `end` is exclusive.
        *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

        Applies to iMessage recipients only, matching `mention` — SMS and RCS recipients
        receive the text with no highlight.

      - `text_decorations?: Array<TextDecoration>`

        Optional array of text decorations applied to character ranges in the `value` field (iMessage only).

        Each decoration specifies a character range `[start, end)` and exactly one of `style` or `animation`.

        **Styles:** `bold`, `italic`, `strikethrough`, `underline`
        **Animations:** `big`, `small`, `shake`, `nod`, `explode`, `ripple`, `bloom`, `jitter`

        Style ranges may overlap (e.g. bold + italic on the same text), but animation ranges must not overlap with other animations or styles.

        *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

        **Note:** decorations render per recipient, not per message. In a group containing
        both iMessage and SMS/RCS participants, iMessage recipients see the decorations and
        SMS/RCS recipients receive the same message as plain text.

        - `range: Array<number>`

          Character range `[start, end)` in the `value` string where the decoration applies.
          `start` is inclusive, `end` is exclusive.
          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

        - `animation?: "big" | "small" | "shake" | 5 more`

          Animated text effect to apply. Mutually exclusive with `style`.

          - `"big"`

          - `"small"`

          - `"shake"`

          - `"nod"`

          - `"explode"`

          - `"ripple"`

          - `"bloom"`

          - `"jitter"`

        - `style?: "bold" | "italic" | "strikethrough" | "underline"`

          Text style to apply. Mutually exclusive with `animation`.

          - `"bold"`

          - `"italic"`

          - `"strikethrough"`

          - `"underline"`

    - `MediaPart`

      - `type: "media"`

        Indicates this is a media attachment part

        - `"media"`

      - `attachment_id?: string`

        Reference to a file pre-uploaded via `POST /v3/attachments` (optional).
        The file is already stored, so sends using this ID skip the download step —
        useful when sending the same file to many recipients.

        Either `url` or `attachment_id` must be provided, but not both.

      - `sticker?: boolean`

        Send this image as a **sticker** rather than a photo. The recipient can peel it off
        and place it on any message in the conversation, and it renders without a bubble.

        An opaque photo is cut out automatically — the subject is lifted from its background,
        the same way "Add Sticker" does on iOS. An image that already has transparency is
        sent as-is. If no subject can be found, the image sends as an ordinary photo.

        **iMessage only.** On SMS/RCS the flag is ignored and the image sends as a photo.

        Stickers can be combined with a `text` part in the same message; the text arrives as
        its own bubble. To place a sticker *onto* an existing message instead, use
        `POST /v3/messages/{messageId}/reactions` with `type: "sticker"`.

      - `url?: string`

        Any publicly accessible HTTPS URL to the media file. The server downloads and
        sends the file automatically — no pre-upload step required.

        **Size limit:** 10MB maximum for URL-based downloads. For larger files (up to 100MB),
        use the pre-upload flow: `POST /v3/attachments` to get a presigned URL, upload directly,
        then reference by `attachment_id`.

        **Requirements:**

        - URL must use HTTPS
        - File content must be a supported format (the server validates the actual file content)

        **Supported formats:**

        - Images: .jpg, .jpeg, .png, .gif, .heic, .heif, .tif, .tiff, .bmp
        - Videos: .mp4, .mov, .m4v, .mpeg, .mpg, .3gp
        - Audio: .m4a, .mp3, .aac, .caf, .wav, .aiff, .amr
        - Documents: .pdf, .txt, .rtf, .csv, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .pages, .numbers, .key, .epub, .zip, .html, .htm
        - Contact & Calendar: .vcf, .ics

        **Tip:** Audio sent here appears as a regular file attachment. To send audio as an
        iMessage voice memo bubble (with inline playback), use `/v3/chats/{chatId}/voicememo`.
        For repeated sends of the same file, use `attachment_id` to avoid redundant downloads.

        Either `url` or `attachment_id` must be provided, but not both.

    - `LinkPart`

      - `type: "link"`

        Indicates this is a rich link preview part

        - `"link"`

      - `value: string`

        URL to send with a rich link preview. The recipient will see an inline card
        with the page's title, description, and preview image (when available).

        A `link` part must be the **only** part in the message. To send a URL as plain
        text (no preview card), use a `text` part instead.

    - `IMessageAppPart`

      An iMessage app card, backed by a Messages app extension. iMessage only —
      an `imessage_app` part must be the **only** part in the message and is never delivered over
      SMS/RCS. See the IMessageAppServiceUnsupported (2018) and RecipientUnsupportedMessageType
      (4005) error codes.

      - `app: App`

        Identifies the iMessage app (Messages app extension) that backs the card.

        - `bundle_id: string`

          Bundle identifier of the Messages app extension. Must not contain `:`.

        - `name: string`

          Display name of the app, shown by Messages' fallback UI.

        - `team_id: string`

          The app's 10-character uppercase alphanumeric team identifier.

        - `app_store_id?: number`

          The owning app's App Store id (optional). When set, recipients without the iMessage app
          installed see a "Get the app" affordance.

      - `layout: Layout`

        Visible layout of the card. At least one of
        `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
        set, otherwise the card renders as an empty bubble.

        `image_url` displays a preview image at the top of the card. The image renders on the
        recipient's card whether or not they have your app installed. The small icon beside the
        caption is the app's own icon and is not settable here.

        `* Note - requires a trusted chat w/ inbound activity`

        `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
        beneath it). They only appear when `image_url` is set — without an image there is nothing to
        overlay — so setting either without `image_url` is rejected.

        - `caption?: string`

          Primary label, top-left and bold.

        - `image_subtitle?: string`

          Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

        - `image_title?: string`

          Bold text overlaid on the card image. Requires `image_url` (rejected without it).

        - `image_url?: string`

          URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

        - `subcaption?: string`

          Secondary label, below `caption` on the left.

        - `trailing_caption?: string`

          Label shown top-right.

        - `trailing_subcaption?: string`

          Label shown below `trailing_caption`, on the right.

      - `type: "imessage_app"`

        Indicates this is an iMessage app card part.

        - `"imessage_app"`

      - `fallback_text?: string`

        Text shown on surfaces that cannot render the card (notifications, lock screen). Defaults
        to the caption when omitted.

      - `interactive?: boolean`

        Whether the card renders as your app's interactive balloon for recipients who have your
        iMessage app installed. `true` (default) lets your installed extension draw its live,
        interactive view for those recipients; everyone else sees the static card built from
        `layout`. `false` always shows the static `layout` card, even to recipients who have the
        app installed. Recipients without your app always see the static card regardless of this
        flag.

      - `url?: string`

        URL the recipient's app opens when they tap the card. Either an absolute `https://` URL
        (capped at 2048 characters) or a `data:` URL carrying inline app state, e.g. a game's
        encoded state (capped at 16384 characters).

    - `AppClipPart`

      Sends a **registered App Clip** — not only Linq's Apple Pay checkout, but any
      partner's own App Clip. `caption` is optional.

      An `app_clip` part must be the **only** part in the message.

      **iMessage only**, and it never downgrades. A `service_preference` of
      `sms` or `rcs` is rejected (`AppClipServiceUnsupported`, 2028). A
      recipient who can't receive it fails the send rather than being sent a
      plain link in its place.

      - `type: "app_clip"`

        Indicates this is an App Clip card

        - `"app_clip"`

      - `value: string`

        An https link whose page is a registered App Clip — Linq's checkout link
        (e.g. the `checkout_url` from `POST /v3/payment_requests`) or a partner's
        own App Clip URL. A URL that doesn't resolve to a sendable App Clip page is
        rejected.

      - `caption?: string`

        Optional caption for the card's **Open** button row. Omit it and the
        card uses the App Clip's own default (`Tap open`). Set it to override
        that with your own short call to action.

  - `preferred_service?: ServiceType`

    Messaging service type

    - `"iMessage"`

    - `"SMS"`

    - `"RCS"`

  - `reply_to?: ReplyTo`

    Reply to another message to create a threaded conversation

    - `message_id: string`

      The ID of the message to reply to

    - `part_index?: number`

      The specific message part to reply to (0-based index).
      Defaults to 0 (first part) if not provided.
      Use this when replying to a specific part of a multipart message.

### Text Part

- `TextPart`

  - `type: "text"`

    Indicates this is a text message part

    - `"text"`

  - `value: string`

    The text content of the message. This value is sent as-is with no parsing or transformation — Markdown syntax will be delivered as plain text. Use `text_decorations` to apply inline formatting and animations (iMessage only).

  - `mention?: string`

    Mention a chat participant. Group chats only — sending a mention to a direct
    chat is rejected with `409` / `2023`. The chat's service is not a constraint:
    a mention is accepted in any group, including one with SMS/RCS participants.

    Set to their handle — E.164 phone number or Apple ID email. `value` is the
    display text; use the bare name (`"Juan"`, not `"@Juan"`). By default the entire
    `value` renders as the mention; use `mention_range` to highlight only part of it.

    Rendering is per recipient, not per message. iMessage recipients see the mention
    highlighted and are notified even if they have muted the chat. SMS and RCS
    recipients receive the same message as plain text — no highlight, and no mute
    override. One send, two experiences.

  - `mention_range?: Array<number>`

    Optional character range `[start, end)` in `value` that renders as the `mention`
    highlight (e.g. just the name in `"Hey Kevin, can you look at this?"`). Requires
    `mention`. Without it, the entire `value` is highlighted. `start` is inclusive,
    `end` is exclusive.
    *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

    Applies to iMessage recipients only, matching `mention` — SMS and RCS recipients
    receive the text with no highlight.

  - `text_decorations?: Array<TextDecoration>`

    Optional array of text decorations applied to character ranges in the `value` field (iMessage only).

    Each decoration specifies a character range `[start, end)` and exactly one of `style` or `animation`.

    **Styles:** `bold`, `italic`, `strikethrough`, `underline`
    **Animations:** `big`, `small`, `shake`, `nod`, `explode`, `ripple`, `bloom`, `jitter`

    Style ranges may overlap (e.g. bold + italic on the same text), but animation ranges must not overlap with other animations or styles.

    *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

    **Note:** decorations render per recipient, not per message. In a group containing
    both iMessage and SMS/RCS participants, iMessage recipients see the decorations and
    SMS/RCS recipients receive the same message as plain text.

    - `range: Array<number>`

      Character range `[start, end)` in the `value` string where the decoration applies.
      `start` is inclusive, `end` is exclusive.
      *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

    - `animation?: "big" | "small" | "shake" | 5 more`

      Animated text effect to apply. Mutually exclusive with `style`.

      - `"big"`

      - `"small"`

      - `"shake"`

      - `"nod"`

      - `"explode"`

      - `"ripple"`

      - `"bloom"`

      - `"jitter"`

    - `style?: "bold" | "italic" | "strikethrough" | "underline"`

      Text style to apply. Mutually exclusive with `animation`.

      - `"bold"`

      - `"italic"`

      - `"strikethrough"`

      - `"underline"`

### Chat Create Response

- `ChatCreateResponse`

  Response for creating a new chat with an initial message

  - `chat: Chat`

    - `id: string`

      Unique identifier for the created chat (UUID)

    - `display_name: string | null`

      Display name for the chat. Defaults to a comma-separated list of recipient handles. Can be updated for group chats.

    - `handles: Array<ChatHandle>`

      List of participants in the chat. Always contains at least two handles (your phone number and the other participant).

      - `id: string`

        Unique identifier for this handle

      - `handle: string`

        Phone number (E.164) or email address of the participant

      - `joined_at: string`

        When this participant joined the chat

      - `service: ServiceType`

        Messaging service type

        - `"iMessage"`

        - `"SMS"`

        - `"RCS"`

      - `is_me?: boolean | null`

        Whether this handle belongs to the sender (your phone number)

      - `left_at?: string | null`

        When they left (if applicable)

      - `status?: "active" | "left" | "removed" | null`

        Participant status

        - `"active"`

        - `"left"`

        - `"removed"`

    - `health_status: HealthStatus`

      **[BETA]** Current health for a chat. Always present — chats start at `HEALTHY` and may shift based on engagement and delivery signals on the conversation. Many `AT_RISK` or `CRITICAL` chats on a single line increase the risk of line flagging.

      Switch on `status` to surface chat and line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. To gate a send, act on the response rather than the status: a `403` is the authoritative answer.

      See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each status means and how to react.

      - `doc_url: string`

        Deep-link to the relevant section of the Chat Health guide for this status.

      - `status: "HEALTHY" | "AT_RISK" | "CRITICAL" | "OPTED_OUT"`

        Current health bucket for the chat. See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section.

        `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`, `END`, or `QUIT`.
        The keyword must be the whole trimmed message, never part of a longer one: `STOP` counts, `please stop`
        does not. Most keywords must match exactly, including case. `OPT OUT` is the exception — it matches in any
        casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all count. It clears as
        soon as they reply again: any later message from them that is not itself an opt-out keyword opts them back
        in immediately — a reply in any conversation with you counts, the same way the block does.

        `OPTED_OUT` marks only the conversation the keyword arrived in. The block below is wider than the mark, so
        a conversation still reading `HEALTHY` can be blocked as well — gate on the `403`, not on the status.
        Group threads are never marked and are never blocked.

        Linq enforces this: while a recipient is opted out, every send to them is rejected with `403` (error code
        `2024`) before the message is queued, across every chat and every line on your account. Nothing is
        delivered, including a final courtesy message — to send one, set `override_optout: true` on that single
        request.

        - `"HEALTHY"`

        - `"AT_RISK"`

        - `"CRITICAL"`

        - `"OPTED_OUT"`

      - `updated_at: string`

        When this status last changed.

    - `is_group: boolean`

      Whether this is a group chat

    - `message: SentMessage`

      A message that was sent (used in CreateChat and SendMessage responses)

      - `id: string`

        Message identifier (UUID)

      - `created_at: string`

        When the message was created

      - `delivery_status: "pending" | "queued" | "sent" | 4 more`

        Current delivery status of a message

        - `"pending"`

        - `"queued"`

        - `"sent"`

        - `"delivered"`

        - `"received"`

        - `"read"`

        - `"failed"`

      - `is_read: boolean`

        DEPRECATED: Use `delivery_status == "read"` instead. Whether the message has been read.

      - `parts: Array<TextPartResponse | MediaPartResponse | LinkPartResponse | 2 more>`

        Message parts in order (text, media, and link)

        - `TextPartResponse`

          A text message part

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

              - `id: string`

                Unique identifier for this handle

              - `handle: string`

                Phone number (E.164) or email address of the participant

              - `joined_at: string`

                When this participant joined the chat

              - `service: ServiceType`

                Messaging service type

              - `is_me?: boolean | null`

                Whether this handle belongs to the sender (your phone number)

              - `left_at?: string | null`

                When they left (if applicable)

              - `status?: "active" | "left" | "removed" | null`

                Participant status

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

              - `"love"`

              - `"like"`

              - `"dislike"`

              - `"laugh"`

              - `"emphasize"`

              - `"question"`

              - `"custom"`

              - `"sticker"`

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

              - `file_name?: string`

                Filename of the sticker

              - `height?: number`

                Sticker image height in pixels

              - `mime_type?: string`

                MIME type of the sticker image

              - `url?: string`

                Presigned URL for downloading the sticker image (expires in 1 hour).

              - `width?: number`

                Sticker image width in pixels

          - `type: "text"`

            Indicates this is a text message part

            - `"text"`

          - `value: string`

            The text content

          - `mention?: string | null`

            DEPRECATED: Use `mentions` instead. Handle (E.164 phone number or Apple ID email)
            of the **first** mention on this part. A part may carry several mentions; this
            field shows only the first in `value` order, so it cannot be used to determine
            whether a given participant was mentioned. `null` when the part carries no mention.

          - `mention_range?: Array<number> | null`

            DEPRECATED: Use `mentions[].range` instead. Character range `[start, end)` in
            `value` highlighted as the **first** mention only. `null` when the range was
            omitted (the whole `value` is highlighted) or the part carries no mention.
            *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `mentions?: Array<Mention> | null`

            Every mention on this part, in the order they appear in `value`. `null` when the
            part carries no mention. A part can carry several mentions of different people —
            check `is_me` to tell whether this line was one of them.

            Only iMessage carries mentions. On a received message this is populated when the
            sender was on iMessage; SMS and RCS have no way to mark a mention, so a message
            from an SMS or RCS participant arrives as plain text with `mentions` null, even in
            a group where other participants are on iMessage.

            - `handle: string`

              Address of the mentioned participant, exactly as the device recorded it — an E.164
              phone number or an email address.

            - `is_me: boolean`

              Whether the mentioned participant is this line.

            - `range: Array<number>`

              Character range `[start, end)` in `value` highlighted as this mention.
              *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `text_decorations?: Array<TextDecoration> | null`

            Text decorations applied to character ranges in the value

            - `range: Array<number>`

              Character range `[start, end)` in the `value` string where the decoration applies.
              `start` is inclusive, `end` is exclusive.
              *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

            - `animation?: "big" | "small" | "shake" | 5 more`

              Animated text effect to apply. Mutually exclusive with `style`.

              - `"big"`

              - `"small"`

              - `"shake"`

              - `"nod"`

              - `"explode"`

              - `"ripple"`

              - `"bloom"`

              - `"jitter"`

            - `style?: "bold" | "italic" | "strikethrough" | "underline"`

              Text style to apply. Mutually exclusive with `animation`.

              - `"bold"`

              - `"italic"`

              - `"strikethrough"`

              - `"underline"`

        - `MediaPartResponse`

          A media attachment part

          - `id: string`

            Unique attachment identifier

          - `filename: string`

            Original filename

          - `mime_type: string`

            MIME type of the file

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `size_bytes: number`

            File size in bytes

          - `type: "media"`

            Indicates this is a media attachment part

            - `"media"`

          - `url: string`

            Presigned URL for downloading the attachment (expires in 1 hour).

        - `LinkPartResponse`

          A rich link preview part

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `type: "link"`

            Indicates this is a rich link preview part

            - `"link"`

          - `value: string`

            The URL

        - `IMessageAppPartResponse`

          An iMessage app card part.

          - `app: App`

            Identifies the iMessage app (Messages app extension) that backs the card.

            - `bundle_id: string`

              Bundle identifier of the Messages app extension. Must not contain `:`.

            - `name: string`

              Display name of the app, shown by Messages' fallback UI.

            - `team_id: string`

              The app's 10-character uppercase alphanumeric team identifier.

            - `app_store_id?: number`

              The owning app's App Store id (optional). When set, recipients without the iMessage app
              installed see a "Get the app" affordance.

          - `layout: Layout`

            Visible layout of the card. At least one of
            `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
            set, otherwise the card renders as an empty bubble.

            `image_url` displays a preview image at the top of the card. The image renders on the
            recipient's card whether or not they have your app installed. The small icon beside the
            caption is the app's own icon and is not settable here.

            `* Note - requires a trusted chat w/ inbound activity`

            `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
            beneath it). They only appear when `image_url` is set — without an image there is nothing to
            overlay — so setting either without `image_url` is rejected.

            - `caption?: string`

              Primary label, top-left and bold.

            - `image_subtitle?: string`

              Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

            - `image_title?: string`

              Bold text overlaid on the card image. Requires `image_url` (rejected without it).

            - `image_url?: string`

              URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

            - `subcaption?: string`

              Secondary label, below `caption` on the left.

            - `trailing_caption?: string`

              Label shown top-right.

            - `trailing_subcaption?: string`

              Label shown below `trailing_caption`, on the right.

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `type: "imessage_app"`

            Indicates this is an iMessage app card part.

            - `"imessage_app"`

          - `url: string`

            The URL delivered to the iMessage app on tap.

          - `fallback_text?: string | null`

            Fallback text for surfaces that cannot render the card.

        - `AppClipPartResponse`

          An App Clip card part

          - `reactions: Array<Reaction> | null`

            Reactions on this message part

            - `handle: ChatHandle`

            - `is_me: boolean`

              Whether this reaction is from the current user

            - `type: ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `id?: string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `custom_emoji?: string | null`

              Custom emoji if type is "custom", null otherwise

            - `sticker?: Sticker | null`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `type: "app_clip"`

            Indicates this is an App Clip card part

            - `"app_clip"`

          - `value: string`

            The App Clip link the card opens

          - `description?: string`

            The card's summary line, composed by Linq from the App Clip page

          - `image_url?: string`

            The card's preview image

          - `title?: string`

            The card's headline, composed by Linq from the App Clip page

      - `sent_at: string | null`

        When the message was actually sent (null if still queued)

      - `delivered_at?: string | null`

        When the message was delivered

      - `effect?: MessageEffect | null`

        iMessage effect applied to a message (screen or bubble effect)

        - `name?: string`

          Name of the effect. Common values:

          - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
          - Bubble effects: slam, loud, gentle, invisible

        - `type?: "screen" | "bubble"`

          Type of effect

          - `"screen"`

          - `"bubble"`

      - `from_handle?: ChatHandle | null`

        The sender of this message as a full handle object

      - `preferred_service?: ServiceType | null`

        Messaging service type

      - `reply_to?: ReplyTo | null`

        Indicates this message is a threaded reply to another message

        - `message_id: string`

          The ID of the message to reply to

        - `part_index?: number`

          The specific message part to reply to (0-based index).
          Defaults to 0 (first part) if not provided.
          Use this when replying to a specific part of a multipart message.

      - `service?: ServiceType | null`

        Messaging service type

    - `service: ServiceType`

      Messaging service type

### Chat Update Response

- `ChatUpdateResponse`

  - `chat_id?: string`

  - `status?: string`

### Chat Leave Chat Response

- `ChatLeaveChatResponse`

  - `message?: string`

  - `status?: string`

  - `trace_id?: string`

### Chat Send Voicememo Response

- `ChatSendVoicememoResponse`

  Response for sending a voice memo to a chat

  - `voice_memo: VoiceMemo`

    - `id: string`

      Message identifier

    - `chat: Chat`

      - `id: string`

        Chat identifier

      - `handles: Array<ChatHandle>`

        Chat participants

        - `id: string`

          Unique identifier for this handle

        - `handle: string`

          Phone number (E.164) or email address of the participant

        - `joined_at: string`

          When this participant joined the chat

        - `service: ServiceType`

          Messaging service type

          - `"iMessage"`

          - `"SMS"`

          - `"RCS"`

        - `is_me?: boolean | null`

          Whether this handle belongs to the sender (your phone number)

        - `left_at?: string | null`

          When they left (if applicable)

        - `status?: "active" | "left" | "removed" | null`

          Participant status

          - `"active"`

          - `"left"`

          - `"removed"`

      - `is_active: boolean`

        Whether the chat is active

      - `is_group: boolean`

        Whether this is a group chat

      - `service: ServiceType`

        Messaging service type

    - `created_at: string`

      When the voice memo was created

    - `from: string`

      Sender phone number

    - `status: string`

      Current delivery status

    - `to: Array<string>`

      Recipient handles (phone numbers or email addresses)

    - `voice_memo: VoiceMemo`

      - `id: string`

        Attachment identifier

      - `filename: string`

        Original filename

      - `mime_type: string`

        Audio MIME type

      - `size_bytes: number`

        File size in bytes

      - `url: string`

        CDN URL for downloading the voice memo

      - `duration_ms?: number | null`

        Duration in milliseconds

    - `service?: ServiceType | null`

      Messaging service type

# Participants

## Add a participant to a chat

`client.chats.participants.add(stringchatID, ParticipantAddParamsbody, RequestOptionsoptions?): ParticipantAddResponse`

**post** `/v3/chats/{chatId}/participants`

Add a new participant to an existing group chat.

**Requirements:**

- Group chats only (3+ existing participants)
- New participant must support the same messaging service as the group
- Cross-service additions not allowed (e.g., can't add RCS-only user to iMessage group)
- For cross-service scenarios, create a new chat instead

### Parameters

- `chatID: string`

- `body: ParticipantAddParams`

  - `handle: string`

    Phone number (E.164 format) or email address of the participant to add

### Returns

- `ParticipantAddResponse`

  - `message?: string`

  - `status?: string`

  - `trace_id?: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const response = await client.chats.participants.add('550e8400-e29b-41d4-a716-446655440000', {
  handle: '+12052499136',
});

console.log(response.trace_id);
```

#### Response

```json
{
  "message": "Participant addition queued",
  "status": "accepted",
  "trace_id": "trace_id"
}
```

## Remove a participant from a chat

`client.chats.participants.remove(stringchatID, ParticipantRemoveParamsbody, RequestOptionsoptions?): ParticipantRemoveResponse`

**delete** `/v3/chats/{chatId}/participants`

Remove a participant from an existing group chat.

**Requirements:**

- Group chats only
- Must have 3+ participants after removal

### Parameters

- `chatID: string`

- `body: ParticipantRemoveParams`

  - `handle: string`

    Phone number (E.164 format) or email address of the participant to remove

### Returns

- `ParticipantRemoveResponse`

  - `message?: string`

  - `status?: string`

  - `trace_id?: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const participant = await client.chats.participants.remove('550e8400-e29b-41d4-a716-446655440000', {
  handle: '+12052499136',
});

console.log(participant.trace_id);
```

#### Response

```json
{
  "message": "Participant removal queued",
  "status": "accepted",
  "trace_id": "trace_id"
}
```

## Domain Types

### Participant Add Response

- `ParticipantAddResponse`

  - `message?: string`

  - `status?: string`

  - `trace_id?: string`

### Participant Remove Response

- `ParticipantRemoveResponse`

  - `message?: string`

  - `status?: string`

  - `trace_id?: string`

# Typing

## Start typing indicator

`client.chats.typing.start(stringchatID, RequestOptionsoptions?): void`

**post** `/v3/chats/{chatId}/typing`

Send a typing indicator to show that someone is typing in the chat.

## Behavior

Typing indicators are best-effort signals that behave as follows:

- **iMessage chats only:** Typing indicators are only supported for iMessage chats.
  Requests for RCS or SMS chats are accepted (`204`) but no indicator is delivered.

- **Send a message first for reliable delivery:** Typing indicators are best-effort.
  If you have not sent a message in this chat recently (roughly the **last 5 minutes**),
  a typing indicator may not reach the recipient — the request is still accepted (`204`),
  but delivery is not deterministic. Once you have sent a message in the chat, typing
  indicators reliably reach the recipient.

- **No delivery guarantee:** Even for active chats, a `204` response only indicates
  the request was accepted for processing.

- **Direct and group chats:** Typing indicators work in both direct and group chats.

## Duration & keeping it visible

- A single call shows the indicator for about **85–90 seconds**, then it clears
  automatically.

- To keep it visible longer, call this endpoint again every **60 seconds**. Each call
  refreshes the indicator so it stays visible continuously.

- Sending a message clears the indicator.

- To resume typing after sending a message, call this endpoint again.

- Incoming messages do not affect the indicator.

## Recipient re-opening the chat

If the recipient brings their messaging app to the foreground while the chat has an
unread message, their device clears any showing typing indicator. Calling this endpoint
again on its own may not bring it back. To make it reappear, either send a message, or
call `DELETE /v3/chats/{chatId}/typing` (stop) and then call start typing again.

## Recommended usage

Call this endpoint when composing begins, call it again every 60 seconds while
composing, and send the message to clear the indicator. To clear the indicator without
sending a message, call `DELETE /v3/chats/{chatId}/typing`.

### Parameters

- `chatID: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

await client.chats.typing.start('550e8400-e29b-41d4-a716-446655440000');
```

#### Response

```json
{
  "error": {
    "status": 400,
    "code": 1002,
    "message": "Phone number must be in E.164 format",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/1xxx/1002/"
  },
  "success": false
}
```

## Stop typing indicator

`client.chats.typing.stop(stringchatID, RequestOptionsoptions?): void`

**delete** `/v3/chats/{chatId}/typing`

Immediately clears the typing indicator for the chat, without sending a message.

The typing indicator also clears automatically when you send a message, or about
85–90 seconds after the last `POST /v3/chats/{chatId}/typing` (start typing) request.

See the start typing endpoint (`POST /v3/chats/{chatId}/typing`) above for behavior
details.

**Note:** Works in both direct and group chats.

### Parameters

- `chatID: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

await client.chats.typing.stop('550e8400-e29b-41d4-a716-446655440000');
```

#### Response

```json
{
  "error": {
    "status": 400,
    "code": 1002,
    "message": "Phone number must be in E.164 format",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/1xxx/1002/"
  },
  "success": false
}
```

# Messages

## Send a message to an existing chat

`client.chats.messages.send(stringchatID, MessageSendParamsbody, RequestOptionsoptions?): MessageSendResponse`

**post** `/v3/chats/{chatId}/messages`

Send a message to an existing chat. Use this endpoint when you already have
a chat ID and want to send additional messages to it.

## Message Effects

You can add iMessage effects to make your messages more expressive. Effects are
optional and can be either screen effects (full-screen animations) or bubble effects
(message bubble animations).

**Screen Effects:** `confetti`, `fireworks`, `lasers`, `sparkles`, `celebration`,
`hearts`, `love`, `balloons`, `happy_birthday`, `echo`, `spotlight`

**Bubble Effects:** `slam`, `loud`, `gentle`, `invisible`

Only one effect type can be applied per message.

## Inline Text Decorations (iMessage only)

Use the `text_decorations` array on a text part to apply styling and animations to character ranges.

Each decoration specifies a `range: [start, end)` and exactly one of `style` or `animation`.

**Styles:** `bold`, `italic`, `strikethrough`, `underline`
**Animations:** `big`, `small`, `shake`, `nod`, `explode`, `ripple`, `bloom`, `jitter`

```json
{
  "type": "text",
  "value": "Hello world",
  "text_decorations": [
    { "range": [0, 5], "style": "bold" },
    { "range": [6, 11], "animation": "shake" }
  ]
}
```

**Note:** Style ranges (bold, italic, etc.) may overlap, but animation ranges must not overlap with other animations or styles. Decorations render per recipient, not per message:
in a group with both iMessage and SMS/RCS participants, iMessage recipients see the decorations and SMS/RCS recipients receive the same message as plain text.

### Parameters

- `chatID: string`

- `body: MessageSendParams`

  - `message: MessageContent`

    Message content container. Groups all message-related fields together,
    separating the "what" (message content) from the "where" (routing fields like from/to).

    A message carries EITHER `parts` — text and attachments, which compose
    into one bubble — or a single `experience` invocation, which renders an
    experience inside Linq's iMessage app. Never both: an app card is the whole message
    (Apple's `MSMessage` cannot coexist with text), so copy and a card are
    two sends, not one.

    - `effect?: MessageEffect`

      iMessage effect to apply to this message (screen or bubble effect)

      - `name?: string`

        Name of the effect. Common values:

        - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
        - Bubble effects: slam, loud, gentle, invisible

      - `type?: "screen" | "bubble"`

        Type of effect

        - `"screen"`

        - `"bubble"`

    - `experience?: Experience`

      Invokes an action on an experience — a third party that renders inside
      Linq's iMessage app. Linq resolves the recipient's connection, mints any
      session the action needs, composes the card and sends it; none of that
      is visible to you.

      Call `GET /v3/experiences/{experience}` for the actions you may invoke
      and the fields each accepts.

      - `action: string`

        Which of its actions, e.g. `attach_card`.

      - `name: string`

        The experience to invoke, e.g. `agentcard` or `agentpay`.

      - `params?: Record<string, unknown>`

        Values for the fields this action exposes. Keys are exactly the
        field names listed for the action — no mapping, no nesting.

        Display copy only, except a `url`-type field — that value sets the
        destination, and must be an absolute `https` URL.

        Some fields are read rather than sent: `agentpay`'s
        `request_payment` takes only a `checkout_url` and resolves the
        amount and reason from that payment request itself, so the card
        cannot state a figure the checkout will not charge.

    - `idempotency_key?: string`

      Optional idempotency key for this message.
      Use this to prevent duplicate sends of the same message. Reusing a key
      whose message was deleted — or was an ephemeral message that has since
      expired — returns 404; the message is never resent.

    - `parts?: Array<TextPart | MediaPart | LinkPart | 2 more>`

      Array of message parts. Each part can be text, media, or link.
      Parts are displayed in order. Text and media can be mixed freely,
      but a `link` part must be the only part in the message.

      **Rich Link Previews:**

      - Use a `link` part to send a URL with a rich preview card
      - A `link` part must be the **only** part in the message
      - To send a URL as plain text (no preview), use a `text` part instead

      **App Clip Payment Cards:**

      - Use an `app_clip` part to send a Linq checkout link as an Apple Pay
        App Clip card (the payment preview with the Open button)
      - An `app_clip` part must be the **only** part in the message
      - iMessage-only: unlike `link`, it never downgrades to SMS/RCS — the
        send fails instead of delivering a bare URL

      **Supported Media:**

      - Images: .jpg, .jpeg, .png, .gif, .heic, .heif, .tif, .tiff, .bmp
      - Videos: .mp4, .mov, .m4v, .mpeg, .mpg, .3gp
      - Audio: .m4a, .mp3, .aac, .caf, .wav, .aiff, .amr
      - Documents: .pdf, .txt, .rtf, .csv, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .pages, .numbers, .key, .epub, .zip, .html, .htm
      - Contact & Calendar: .vcf, .ics

      **Audio:**

      - Audio files (.m4a, .mp3, .aac, .caf, .wav, .aiff, .amr) are fully supported as media parts
      - To send audio as an **iMessage voice memo bubble** (inline playback UI), use the dedicated
        `/v3/chats/{chatId}/voicememo` endpoint instead

      **Validation Rules:**

      - A `link` part must be the **only** part in the message. It cannot be combined
        with text or media parts.
      - An `app_clip` part must be the **only** part in the message. Its `value`
        must be a Linq checkout link (e.g. from `POST /v3/payment_requests`);
        any other URL is rejected.
      - Consecutive text parts are not allowed. Text parts must be separated by
        media parts. For example, [text, text] is invalid, but [text, media, text] is valid.
      - Maximum of **100 parts** total.
      - Media parts using a public `url` (downloaded by the server on send) are
        capped at **40**. Parts using `attachment_id` or presigned URLs
        are exempt from this sub-limit. For bulk media sends exceeding 40 files,
        pre-upload via `POST /v3/attachments` and reference by `attachment_id` or `download_url`.

      - `TextPart`

        - `type: "text"`

          Indicates this is a text message part

          - `"text"`

        - `value: string`

          The text content of the message. This value is sent as-is with no parsing or transformation — Markdown syntax will be delivered as plain text. Use `text_decorations` to apply inline formatting and animations (iMessage only).

        - `mention?: string`

          Mention a chat participant. Group chats only — sending a mention to a direct
          chat is rejected with `409` / `2023`. The chat's service is not a constraint:
          a mention is accepted in any group, including one with SMS/RCS participants.

          Set to their handle — E.164 phone number or Apple ID email. `value` is the
          display text; use the bare name (`"Juan"`, not `"@Juan"`). By default the entire
          `value` renders as the mention; use `mention_range` to highlight only part of it.

          Rendering is per recipient, not per message. iMessage recipients see the mention
          highlighted and are notified even if they have muted the chat. SMS and RCS
          recipients receive the same message as plain text — no highlight, and no mute
          override. One send, two experiences.

        - `mention_range?: Array<number>`

          Optional character range `[start, end)` in `value` that renders as the `mention`
          highlight (e.g. just the name in `"Hey Kevin, can you look at this?"`). Requires
          `mention`. Without it, the entire `value` is highlighted. `start` is inclusive,
          `end` is exclusive.
          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          Applies to iMessage recipients only, matching `mention` — SMS and RCS recipients
          receive the text with no highlight.

        - `text_decorations?: Array<TextDecoration>`

          Optional array of text decorations applied to character ranges in the `value` field (iMessage only).

          Each decoration specifies a character range `[start, end)` and exactly one of `style` or `animation`.

          **Styles:** `bold`, `italic`, `strikethrough`, `underline`
          **Animations:** `big`, `small`, `shake`, `nod`, `explode`, `ripple`, `bloom`, `jitter`

          Style ranges may overlap (e.g. bold + italic on the same text), but animation ranges must not overlap with other animations or styles.

          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          **Note:** decorations render per recipient, not per message. In a group containing
          both iMessage and SMS/RCS participants, iMessage recipients see the decorations and
          SMS/RCS recipients receive the same message as plain text.

          - `range: Array<number>`

            Character range `[start, end)` in the `value` string where the decoration applies.
            `start` is inclusive, `end` is exclusive.
            *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `animation?: "big" | "small" | "shake" | 5 more`

            Animated text effect to apply. Mutually exclusive with `style`.

            - `"big"`

            - `"small"`

            - `"shake"`

            - `"nod"`

            - `"explode"`

            - `"ripple"`

            - `"bloom"`

            - `"jitter"`

          - `style?: "bold" | "italic" | "strikethrough" | "underline"`

            Text style to apply. Mutually exclusive with `animation`.

            - `"bold"`

            - `"italic"`

            - `"strikethrough"`

            - `"underline"`

      - `MediaPart`

        - `type: "media"`

          Indicates this is a media attachment part

          - `"media"`

        - `attachment_id?: string`

          Reference to a file pre-uploaded via `POST /v3/attachments` (optional).
          The file is already stored, so sends using this ID skip the download step —
          useful when sending the same file to many recipients.

          Either `url` or `attachment_id` must be provided, but not both.

        - `sticker?: boolean`

          Send this image as a **sticker** rather than a photo. The recipient can peel it off
          and place it on any message in the conversation, and it renders without a bubble.

          An opaque photo is cut out automatically — the subject is lifted from its background,
          the same way "Add Sticker" does on iOS. An image that already has transparency is
          sent as-is. If no subject can be found, the image sends as an ordinary photo.

          **iMessage only.** On SMS/RCS the flag is ignored and the image sends as a photo.

          Stickers can be combined with a `text` part in the same message; the text arrives as
          its own bubble. To place a sticker *onto* an existing message instead, use
          `POST /v3/messages/{messageId}/reactions` with `type: "sticker"`.

        - `url?: string`

          Any publicly accessible HTTPS URL to the media file. The server downloads and
          sends the file automatically — no pre-upload step required.

          **Size limit:** 10MB maximum for URL-based downloads. For larger files (up to 100MB),
          use the pre-upload flow: `POST /v3/attachments` to get a presigned URL, upload directly,
          then reference by `attachment_id`.

          **Requirements:**

          - URL must use HTTPS
          - File content must be a supported format (the server validates the actual file content)

          **Supported formats:**

          - Images: .jpg, .jpeg, .png, .gif, .heic, .heif, .tif, .tiff, .bmp
          - Videos: .mp4, .mov, .m4v, .mpeg, .mpg, .3gp
          - Audio: .m4a, .mp3, .aac, .caf, .wav, .aiff, .amr
          - Documents: .pdf, .txt, .rtf, .csv, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .pages, .numbers, .key, .epub, .zip, .html, .htm
          - Contact & Calendar: .vcf, .ics

          **Tip:** Audio sent here appears as a regular file attachment. To send audio as an
          iMessage voice memo bubble (with inline playback), use `/v3/chats/{chatId}/voicememo`.
          For repeated sends of the same file, use `attachment_id` to avoid redundant downloads.

          Either `url` or `attachment_id` must be provided, but not both.

      - `LinkPart`

        - `type: "link"`

          Indicates this is a rich link preview part

          - `"link"`

        - `value: string`

          URL to send with a rich link preview. The recipient will see an inline card
          with the page's title, description, and preview image (when available).

          A `link` part must be the **only** part in the message. To send a URL as plain
          text (no preview card), use a `text` part instead.

      - `IMessageAppPart`

        An iMessage app card, backed by a Messages app extension. iMessage only —
        an `imessage_app` part must be the **only** part in the message and is never delivered over
        SMS/RCS. See the IMessageAppServiceUnsupported (2018) and RecipientUnsupportedMessageType
        (4005) error codes.

        - `app: App`

          Identifies the iMessage app (Messages app extension) that backs the card.

          - `bundle_id: string`

            Bundle identifier of the Messages app extension. Must not contain `:`.

          - `name: string`

            Display name of the app, shown by Messages' fallback UI.

          - `team_id: string`

            The app's 10-character uppercase alphanumeric team identifier.

          - `app_store_id?: number`

            The owning app's App Store id (optional). When set, recipients without the iMessage app
            installed see a "Get the app" affordance.

        - `layout: Layout`

          Visible layout of the card. At least one of
          `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
          set, otherwise the card renders as an empty bubble.

          `image_url` displays a preview image at the top of the card. The image renders on the
          recipient's card whether or not they have your app installed. The small icon beside the
          caption is the app's own icon and is not settable here.

          `* Note - requires a trusted chat w/ inbound activity`

          `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
          beneath it). They only appear when `image_url` is set — without an image there is nothing to
          overlay — so setting either without `image_url` is rejected.

          - `caption?: string`

            Primary label, top-left and bold.

          - `image_subtitle?: string`

            Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

          - `image_title?: string`

            Bold text overlaid on the card image. Requires `image_url` (rejected without it).

          - `image_url?: string`

            URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

          - `subcaption?: string`

            Secondary label, below `caption` on the left.

          - `trailing_caption?: string`

            Label shown top-right.

          - `trailing_subcaption?: string`

            Label shown below `trailing_caption`, on the right.

        - `type: "imessage_app"`

          Indicates this is an iMessage app card part.

          - `"imessage_app"`

        - `fallback_text?: string`

          Text shown on surfaces that cannot render the card (notifications, lock screen). Defaults
          to the caption when omitted.

        - `interactive?: boolean`

          Whether the card renders as your app's interactive balloon for recipients who have your
          iMessage app installed. `true` (default) lets your installed extension draw its live,
          interactive view for those recipients; everyone else sees the static card built from
          `layout`. `false` always shows the static `layout` card, even to recipients who have the
          app installed. Recipients without your app always see the static card regardless of this
          flag.

        - `url?: string`

          URL the recipient's app opens when they tap the card. Either an absolute `https://` URL
          (capped at 2048 characters) or a `data:` URL carrying inline app state, e.g. a game's
          encoded state (capped at 16384 characters).

      - `AppClipPart`

        Sends a **registered App Clip** — not only Linq's Apple Pay checkout, but any
        partner's own App Clip. `caption` is optional.

        An `app_clip` part must be the **only** part in the message.

        **iMessage only**, and it never downgrades. A `service_preference` of
        `sms` or `rcs` is rejected (`AppClipServiceUnsupported`, 2028). A
        recipient who can't receive it fails the send rather than being sent a
        plain link in its place.

        - `type: "app_clip"`

          Indicates this is an App Clip card

          - `"app_clip"`

        - `value: string`

          An https link whose page is a registered App Clip — Linq's checkout link
          (e.g. the `checkout_url` from `POST /v3/payment_requests`) or a partner's
          own App Clip URL. A URL that doesn't resolve to a sendable App Clip page is
          rejected.

        - `caption?: string`

          Optional caption for the card's **Open** button row. Omit it and the
          card uses the App Clip's own default (`Tap open`). Set it to override
          that with your own short call to action.

    - `preferred_service?: ServiceType`

      Messaging service type

      - `"iMessage"`

      - `"SMS"`

      - `"RCS"`

    - `reply_to?: ReplyTo`

      Reply to another message to create a threaded conversation

      - `message_id: string`

        The ID of the message to reply to

      - `part_index?: number`

        The specific message part to reply to (0-based index).
        Defaults to 0 (first part) if not provided.
        Use this when replying to a specific part of a multipart message.

  - `override_optout?: boolean`

    Send even though the recipient asked you to stop (`403`, error code
    `2024`). Applies to this request only: the opt-out stays in place, so
    the next send without this flag is rejected again. Every override is
    recorded against your API key.

### Returns

- `MessageSendResponse`

  Response for sending a message to a chat

  - `chat_id: string`

    Unique identifier of the chat this message was sent to

  - `message: SentMessage`

    A message that was sent (used in CreateChat and SendMessage responses)

    - `id: string`

      Message identifier (UUID)

    - `created_at: string`

      When the message was created

    - `delivery_status: "pending" | "queued" | "sent" | 4 more`

      Current delivery status of a message

      - `"pending"`

      - `"queued"`

      - `"sent"`

      - `"delivered"`

      - `"received"`

      - `"read"`

      - `"failed"`

    - `is_read: boolean`

      DEPRECATED: Use `delivery_status == "read"` instead. Whether the message has been read.

    - `parts: Array<TextPartResponse | MediaPartResponse | LinkPartResponse | 2 more>`

      Message parts in order (text, media, and link)

      - `TextPartResponse`

        A text message part

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

            - `id: string`

              Unique identifier for this handle

            - `handle: string`

              Phone number (E.164) or email address of the participant

            - `joined_at: string`

              When this participant joined the chat

            - `service: ServiceType`

              Messaging service type

              - `"iMessage"`

              - `"SMS"`

              - `"RCS"`

            - `is_me?: boolean | null`

              Whether this handle belongs to the sender (your phone number)

            - `left_at?: string | null`

              When they left (if applicable)

            - `status?: "active" | "left" | "removed" | null`

              Participant status

              - `"active"`

              - `"left"`

              - `"removed"`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `"love"`

            - `"like"`

            - `"dislike"`

            - `"laugh"`

            - `"emphasize"`

            - `"question"`

            - `"custom"`

            - `"sticker"`

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

            - `file_name?: string`

              Filename of the sticker

            - `height?: number`

              Sticker image height in pixels

            - `mime_type?: string`

              MIME type of the sticker image

            - `url?: string`

              Presigned URL for downloading the sticker image (expires in 1 hour).

            - `width?: number`

              Sticker image width in pixels

        - `type: "text"`

          Indicates this is a text message part

          - `"text"`

        - `value: string`

          The text content

        - `mention?: string | null`

          DEPRECATED: Use `mentions` instead. Handle (E.164 phone number or Apple ID email)
          of the **first** mention on this part. A part may carry several mentions; this
          field shows only the first in `value` order, so it cannot be used to determine
          whether a given participant was mentioned. `null` when the part carries no mention.

        - `mention_range?: Array<number> | null`

          DEPRECATED: Use `mentions[].range` instead. Character range `[start, end)` in
          `value` highlighted as the **first** mention only. `null` when the range was
          omitted (the whole `value` is highlighted) or the part carries no mention.
          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

        - `mentions?: Array<Mention> | null`

          Every mention on this part, in the order they appear in `value`. `null` when the
          part carries no mention. A part can carry several mentions of different people —
          check `is_me` to tell whether this line was one of them.

          Only iMessage carries mentions. On a received message this is populated when the
          sender was on iMessage; SMS and RCS have no way to mark a mention, so a message
          from an SMS or RCS participant arrives as plain text with `mentions` null, even in
          a group where other participants are on iMessage.

          - `handle: string`

            Address of the mentioned participant, exactly as the device recorded it — an E.164
            phone number or an email address.

          - `is_me: boolean`

            Whether the mentioned participant is this line.

          - `range: Array<number>`

            Character range `[start, end)` in `value` highlighted as this mention.
            *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

        - `text_decorations?: Array<TextDecoration> | null`

          Text decorations applied to character ranges in the value

          - `range: Array<number>`

            Character range `[start, end)` in the `value` string where the decoration applies.
            `start` is inclusive, `end` is exclusive.
            *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `animation?: "big" | "small" | "shake" | 5 more`

            Animated text effect to apply. Mutually exclusive with `style`.

            - `"big"`

            - `"small"`

            - `"shake"`

            - `"nod"`

            - `"explode"`

            - `"ripple"`

            - `"bloom"`

            - `"jitter"`

          - `style?: "bold" | "italic" | "strikethrough" | "underline"`

            Text style to apply. Mutually exclusive with `animation`.

            - `"bold"`

            - `"italic"`

            - `"strikethrough"`

            - `"underline"`

      - `MediaPartResponse`

        A media attachment part

        - `id: string`

          Unique attachment identifier

        - `filename: string`

          Original filename

        - `mime_type: string`

          MIME type of the file

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

        - `size_bytes: number`

          File size in bytes

        - `type: "media"`

          Indicates this is a media attachment part

          - `"media"`

        - `url: string`

          Presigned URL for downloading the attachment (expires in 1 hour).

      - `LinkPartResponse`

        A rich link preview part

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

        - `type: "link"`

          Indicates this is a rich link preview part

          - `"link"`

        - `value: string`

          The URL

      - `IMessageAppPartResponse`

        An iMessage app card part.

        - `app: App`

          Identifies the iMessage app (Messages app extension) that backs the card.

          - `bundle_id: string`

            Bundle identifier of the Messages app extension. Must not contain `:`.

          - `name: string`

            Display name of the app, shown by Messages' fallback UI.

          - `team_id: string`

            The app's 10-character uppercase alphanumeric team identifier.

          - `app_store_id?: number`

            The owning app's App Store id (optional). When set, recipients without the iMessage app
            installed see a "Get the app" affordance.

        - `layout: Layout`

          Visible layout of the card. At least one of
          `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
          set, otherwise the card renders as an empty bubble.

          `image_url` displays a preview image at the top of the card. The image renders on the
          recipient's card whether or not they have your app installed. The small icon beside the
          caption is the app's own icon and is not settable here.

          `* Note - requires a trusted chat w/ inbound activity`

          `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
          beneath it). They only appear when `image_url` is set — without an image there is nothing to
          overlay — so setting either without `image_url` is rejected.

          - `caption?: string`

            Primary label, top-left and bold.

          - `image_subtitle?: string`

            Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

          - `image_title?: string`

            Bold text overlaid on the card image. Requires `image_url` (rejected without it).

          - `image_url?: string`

            URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

          - `subcaption?: string`

            Secondary label, below `caption` on the left.

          - `trailing_caption?: string`

            Label shown top-right.

          - `trailing_subcaption?: string`

            Label shown below `trailing_caption`, on the right.

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

        - `type: "imessage_app"`

          Indicates this is an iMessage app card part.

          - `"imessage_app"`

        - `url: string`

          The URL delivered to the iMessage app on tap.

        - `fallback_text?: string | null`

          Fallback text for surfaces that cannot render the card.

      - `AppClipPartResponse`

        An App Clip card part

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

        - `type: "app_clip"`

          Indicates this is an App Clip card part

          - `"app_clip"`

        - `value: string`

          The App Clip link the card opens

        - `description?: string`

          The card's summary line, composed by Linq from the App Clip page

        - `image_url?: string`

          The card's preview image

        - `title?: string`

          The card's headline, composed by Linq from the App Clip page

    - `sent_at: string | null`

      When the message was actually sent (null if still queued)

    - `delivered_at?: string | null`

      When the message was delivered

    - `effect?: MessageEffect | null`

      iMessage effect applied to a message (screen or bubble effect)

      - `name?: string`

        Name of the effect. Common values:

        - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
        - Bubble effects: slam, loud, gentle, invisible

      - `type?: "screen" | "bubble"`

        Type of effect

        - `"screen"`

        - `"bubble"`

    - `from_handle?: ChatHandle | null`

      The sender of this message as a full handle object

    - `preferred_service?: ServiceType | null`

      Messaging service type

    - `reply_to?: ReplyTo | null`

      Indicates this message is a threaded reply to another message

      - `message_id: string`

        The ID of the message to reply to

      - `part_index?: number`

        The specific message part to reply to (0-based index).
        Defaults to 0 (first part) if not provided.
        Use this when replying to a specific part of a multipart message.

    - `service?: ServiceType | null`

      Messaging service type

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const response = await client.chats.messages.send('550e8400-e29b-41d4-a716-446655440000', {
  message: { parts: [{ type: 'text', value: 'Hello, world!' }] },
});

console.log(response.chat_id);
```

#### Response

```json
{
  "chat_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": {
    "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
    "created_at": "2025-10-23T13:07:55.019-05:00",
    "delivery_status": "pending",
    "is_read": false,
    "parts": [
      {
        "reactions": [
          {
            "handle": {
              "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
              "handle": "+15551234567",
              "joined_at": "2025-05-21T15:30:00.000-05:00",
              "service": "iMessage",
              "is_me": false,
              "left_at": "2019-12-27T18:11:19.117Z",
              "status": "active"
            },
            "is_me": false,
            "type": "love",
            "id": "9f8b1c2d-3e4f-5061-7283-94a5b6c7d8e9",
            "custom_emoji": null,
            "sticker": {
              "file_name": "sticker.png",
              "height": 420,
              "mime_type": "image/png",
              "url": "https://cdn.linqapp.com/attachments/a1b2c3d4/sticker.png?signature=...",
              "width": 420
            }
          }
        ],
        "type": "text",
        "value": "Hello!",
        "mention": "+14155551234",
        "mention_range": [
          4,
          9
        ],
        "mentions": [
          {
            "handle": "+14155550123",
            "is_me": true,
            "range": [
              4,
              9
            ]
          }
        ],
        "text_decorations": [
          {
            "range": [
              0,
              5
            ],
            "animation": "shake",
            "style": "bold"
          }
        ]
      }
    ],
    "sent_at": null,
    "delivered_at": null,
    "effect": {
      "name": "confetti",
      "type": "screen"
    },
    "from_handle": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "handle": "+15551234567",
      "joined_at": "2025-05-21T15:30:00.000-05:00",
      "service": "iMessage",
      "is_me": false,
      "left_at": "2019-12-27T18:11:19.117Z",
      "status": "active"
    },
    "preferred_service": "iMessage",
    "reply_to": {
      "message_id": "550e8400-e29b-41d4-a716-446655440000",
      "part_index": 0
    },
    "service": "iMessage"
  }
}
```

## Get messages from a chat

`client.chats.messages.list(stringchatID, MessageListParamsquery?, RequestOptionsoptions?): ListMessagesPagination<Message>`

**get** `/v3/chats/{chatId}/messages`

Retrieve messages from a specific chat with pagination support.

### Parameters

- `chatID: string`

- `query: MessageListParams`

  - `cursor?: string`

    Pagination cursor from previous next_cursor response

  - `limit?: number`

    Maximum number of messages to return

### Returns

- `Message`

  - `id: string`

    Unique identifier for the message

  - `chat_id: string`

    ID of the chat this message belongs to

  - `created_at: string`

    When the message was created

  - `delivery_status: "pending" | "queued" | "sent" | 4 more`

    Current delivery status of a message

    - `"pending"`

    - `"queued"`

    - `"sent"`

    - `"delivered"`

    - `"received"`

    - `"read"`

    - `"failed"`

  - `is_delivered: boolean`

    DEPRECATED: Use `delivery_status` instead (true when `delivery_status` is `delivered` or `read`). Whether the message has been delivered.

  - `is_from_me: boolean`

    Whether this message was sent by the authenticated user

  - `is_read: boolean`

    DEPRECATED: Use `delivery_status == "read"` instead. Whether the message has been read.

  - `updated_at: string`

    When the message was last updated

  - `delivered_at?: string | null`

    When the message was delivered

  - `effect?: MessageEffect | null`

    iMessage effect applied to a message (screen or bubble effect)

    - `name?: string`

      Name of the effect. Common values:

      - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
      - Bubble effects: slam, loud, gentle, invisible

    - `type?: "screen" | "bubble"`

      Type of effect

      - `"screen"`

      - `"bubble"`

  - `from?: string | null`

    DEPRECATED: Use from_handle instead. Phone number of the message sender.

  - `from_handle?: ChatHandle | null`

    The sender of this message as a full handle object

    - `id: string`

      Unique identifier for this handle

    - `handle: string`

      Phone number (E.164) or email address of the participant

    - `joined_at: string`

      When this participant joined the chat

    - `service: ServiceType`

      Messaging service type

      - `"iMessage"`

      - `"SMS"`

      - `"RCS"`

    - `is_me?: boolean | null`

      Whether this handle belongs to the sender (your phone number)

    - `left_at?: string | null`

      When they left (if applicable)

    - `status?: "active" | "left" | "removed" | null`

      Participant status

      - `"active"`

      - `"left"`

      - `"removed"`

  - `parts?: Array<TextPartResponse | MediaPartResponse | LinkPartResponse | 2 more> | null`

    Message parts in order (text, media, and link)

    - `TextPartResponse`

      A text message part

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `"love"`

          - `"like"`

          - `"dislike"`

          - `"laugh"`

          - `"emphasize"`

          - `"question"`

          - `"custom"`

          - `"sticker"`

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `file_name?: string`

            Filename of the sticker

          - `height?: number`

            Sticker image height in pixels

          - `mime_type?: string`

            MIME type of the sticker image

          - `url?: string`

            Presigned URL for downloading the sticker image (expires in 1 hour).

          - `width?: number`

            Sticker image width in pixels

      - `type: "text"`

        Indicates this is a text message part

        - `"text"`

      - `value: string`

        The text content

      - `mention?: string | null`

        DEPRECATED: Use `mentions` instead. Handle (E.164 phone number or Apple ID email)
        of the **first** mention on this part. A part may carry several mentions; this
        field shows only the first in `value` order, so it cannot be used to determine
        whether a given participant was mentioned. `null` when the part carries no mention.

      - `mention_range?: Array<number> | null`

        DEPRECATED: Use `mentions[].range` instead. Character range `[start, end)` in
        `value` highlighted as the **first** mention only. `null` when the range was
        omitted (the whole `value` is highlighted) or the part carries no mention.
        *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

      - `mentions?: Array<Mention> | null`

        Every mention on this part, in the order they appear in `value`. `null` when the
        part carries no mention. A part can carry several mentions of different people —
        check `is_me` to tell whether this line was one of them.

        Only iMessage carries mentions. On a received message this is populated when the
        sender was on iMessage; SMS and RCS have no way to mark a mention, so a message
        from an SMS or RCS participant arrives as plain text with `mentions` null, even in
        a group where other participants are on iMessage.

        - `handle: string`

          Address of the mentioned participant, exactly as the device recorded it — an E.164
          phone number or an email address.

        - `is_me: boolean`

          Whether the mentioned participant is this line.

        - `range: Array<number>`

          Character range `[start, end)` in `value` highlighted as this mention.
          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

      - `text_decorations?: Array<TextDecoration> | null`

        Text decorations applied to character ranges in the value

        - `range: Array<number>`

          Character range `[start, end)` in the `value` string where the decoration applies.
          `start` is inclusive, `end` is exclusive.
          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

        - `animation?: "big" | "small" | "shake" | 5 more`

          Animated text effect to apply. Mutually exclusive with `style`.

          - `"big"`

          - `"small"`

          - `"shake"`

          - `"nod"`

          - `"explode"`

          - `"ripple"`

          - `"bloom"`

          - `"jitter"`

        - `style?: "bold" | "italic" | "strikethrough" | "underline"`

          Text style to apply. Mutually exclusive with `animation`.

          - `"bold"`

          - `"italic"`

          - `"strikethrough"`

          - `"underline"`

    - `MediaPartResponse`

      A media attachment part

      - `id: string`

        Unique attachment identifier

      - `filename: string`

        Original filename

      - `mime_type: string`

        MIME type of the file

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `size_bytes: number`

        File size in bytes

      - `type: "media"`

        Indicates this is a media attachment part

        - `"media"`

      - `url: string`

        Presigned URL for downloading the attachment (expires in 1 hour).

    - `LinkPartResponse`

      A rich link preview part

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `type: "link"`

        Indicates this is a rich link preview part

        - `"link"`

      - `value: string`

        The URL

    - `IMessageAppPartResponse`

      An iMessage app card part.

      - `app: App`

        Identifies the iMessage app (Messages app extension) that backs the card.

        - `bundle_id: string`

          Bundle identifier of the Messages app extension. Must not contain `:`.

        - `name: string`

          Display name of the app, shown by Messages' fallback UI.

        - `team_id: string`

          The app's 10-character uppercase alphanumeric team identifier.

        - `app_store_id?: number`

          The owning app's App Store id (optional). When set, recipients without the iMessage app
          installed see a "Get the app" affordance.

      - `layout: Layout`

        Visible layout of the card. At least one of
        `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
        set, otherwise the card renders as an empty bubble.

        `image_url` displays a preview image at the top of the card. The image renders on the
        recipient's card whether or not they have your app installed. The small icon beside the
        caption is the app's own icon and is not settable here.

        `* Note - requires a trusted chat w/ inbound activity`

        `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
        beneath it). They only appear when `image_url` is set — without an image there is nothing to
        overlay — so setting either without `image_url` is rejected.

        - `caption?: string`

          Primary label, top-left and bold.

        - `image_subtitle?: string`

          Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

        - `image_title?: string`

          Bold text overlaid on the card image. Requires `image_url` (rejected without it).

        - `image_url?: string`

          URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

        - `subcaption?: string`

          Secondary label, below `caption` on the left.

        - `trailing_caption?: string`

          Label shown top-right.

        - `trailing_subcaption?: string`

          Label shown below `trailing_caption`, on the right.

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `type: "imessage_app"`

        Indicates this is an iMessage app card part.

        - `"imessage_app"`

      - `url: string`

        The URL delivered to the iMessage app on tap.

      - `fallback_text?: string | null`

        Fallback text for surfaces that cannot render the card.

    - `AppClipPartResponse`

      An App Clip card part

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `type: "app_clip"`

        Indicates this is an App Clip card part

        - `"app_clip"`

      - `value: string`

        The App Clip link the card opens

      - `description?: string`

        The card's summary line, composed by Linq from the App Clip page

      - `image_url?: string`

        The card's preview image

      - `title?: string`

        The card's headline, composed by Linq from the App Clip page

  - `preferred_service?: ServiceType | null`

    Messaging service type

  - `read_at?: string | null`

    When the message was read

  - `reconciled_at?: string`

    Present only when this message was recovered by reconciliation rather than delivered live, and set to the time of that recovery. The field is omitted entirely for normally-delivered messages, which is the overwhelming majority. When present, expect `sent_at` to be substantially earlier — the message is genuine but was ingested late, so it may not have appeared in earlier reads of this conversation.

  - `reply_to?: ReplyTo | null`

    Indicates this message is a threaded reply to another message

    - `message_id: string`

      The ID of the message to reply to

    - `part_index?: number`

      The specific message part to reply to (0-based index).
      Defaults to 0 (first part) if not provided.
      Use this when replying to a specific part of a multipart message.

  - `sent_at?: string | null`

    When the message was sent

  - `service?: ServiceType | null`

    Messaging service type

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

// Automatically fetches more pages as needed.
for await (const message of client.chats.messages.list('550e8400-e29b-41d4-a716-446655440000')) {
  console.log(message.id);
}
```

#### Response

```json
{
  "messages": [
    {
      "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
      "chat_id": "94c6bf33-31d9-40e3-a0e9-f94250ecedb9",
      "created_at": "2024-01-15T10:30:00Z",
      "delivery_status": "pending",
      "is_delivered": true,
      "is_from_me": true,
      "is_read": false,
      "updated_at": "2024-01-15T10:30:00Z",
      "delivered_at": "2024-01-15T10:30:10Z",
      "effect": {
        "name": "confetti",
        "type": "screen"
      },
      "from": "+12052535597",
      "from_handle": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "handle": "+15551234567",
        "joined_at": "2025-05-21T15:30:00.000-05:00",
        "service": "iMessage",
        "is_me": false,
        "left_at": "2019-12-27T18:11:19.117Z",
        "status": "active"
      },
      "parts": [
        {
          "reactions": [
            {
              "handle": {
                "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
                "handle": "+15551234567",
                "joined_at": "2025-05-21T15:30:00.000-05:00",
                "service": "iMessage",
                "is_me": false,
                "left_at": "2019-12-27T18:11:19.117Z",
                "status": "active"
              },
              "is_me": false,
              "type": "love",
              "id": "9f8b1c2d-3e4f-5061-7283-94a5b6c7d8e9",
              "custom_emoji": null,
              "sticker": {
                "file_name": "sticker.png",
                "height": 420,
                "mime_type": "image/png",
                "url": "https://cdn.linqapp.com/attachments/a1b2c3d4/sticker.png?signature=...",
                "width": 420
              }
            }
          ],
          "type": "text",
          "value": "Hello!",
          "mention": "+14155551234",
          "mention_range": [
            4,
            9
          ],
          "mentions": [
            {
              "handle": "+14155550123",
              "is_me": true,
              "range": [
                4,
                9
              ]
            }
          ],
          "text_decorations": [
            {
              "range": [
                0,
                5
              ],
              "animation": "shake",
              "style": "bold"
            }
          ]
        }
      ],
      "preferred_service": "iMessage",
      "read_at": "2024-01-15T10:35:00Z",
      "reconciled_at": "2024-01-15T14:05:00Z",
      "reply_to": {
        "message_id": "550e8400-e29b-41d4-a716-446655440000",
        "part_index": 0
      },
      "sent_at": "2024-01-15T10:30:05Z",
      "service": "iMessage"
    }
  ],
  "next_cursor": "next_cursor"
}
```

## Domain Types

### Sent Message

- `SentMessage`

  A message that was sent (used in CreateChat and SendMessage responses)

  - `id: string`

    Message identifier (UUID)

  - `created_at: string`

    When the message was created

  - `delivery_status: "pending" | "queued" | "sent" | 4 more`

    Current delivery status of a message

    - `"pending"`

    - `"queued"`

    - `"sent"`

    - `"delivered"`

    - `"received"`

    - `"read"`

    - `"failed"`

  - `is_read: boolean`

    DEPRECATED: Use `delivery_status == "read"` instead. Whether the message has been read.

  - `parts: Array<TextPartResponse | MediaPartResponse | LinkPartResponse | 2 more>`

    Message parts in order (text, media, and link)

    - `TextPartResponse`

      A text message part

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

          - `id: string`

            Unique identifier for this handle

          - `handle: string`

            Phone number (E.164) or email address of the participant

          - `joined_at: string`

            When this participant joined the chat

          - `service: ServiceType`

            Messaging service type

            - `"iMessage"`

            - `"SMS"`

            - `"RCS"`

          - `is_me?: boolean | null`

            Whether this handle belongs to the sender (your phone number)

          - `left_at?: string | null`

            When they left (if applicable)

          - `status?: "active" | "left" | "removed" | null`

            Participant status

            - `"active"`

            - `"left"`

            - `"removed"`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `"love"`

          - `"like"`

          - `"dislike"`

          - `"laugh"`

          - `"emphasize"`

          - `"question"`

          - `"custom"`

          - `"sticker"`

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `file_name?: string`

            Filename of the sticker

          - `height?: number`

            Sticker image height in pixels

          - `mime_type?: string`

            MIME type of the sticker image

          - `url?: string`

            Presigned URL for downloading the sticker image (expires in 1 hour).

          - `width?: number`

            Sticker image width in pixels

      - `type: "text"`

        Indicates this is a text message part

        - `"text"`

      - `value: string`

        The text content

      - `mention?: string | null`

        DEPRECATED: Use `mentions` instead. Handle (E.164 phone number or Apple ID email)
        of the **first** mention on this part. A part may carry several mentions; this
        field shows only the first in `value` order, so it cannot be used to determine
        whether a given participant was mentioned. `null` when the part carries no mention.

      - `mention_range?: Array<number> | null`

        DEPRECATED: Use `mentions[].range` instead. Character range `[start, end)` in
        `value` highlighted as the **first** mention only. `null` when the range was
        omitted (the whole `value` is highlighted) or the part carries no mention.
        *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

      - `mentions?: Array<Mention> | null`

        Every mention on this part, in the order they appear in `value`. `null` when the
        part carries no mention. A part can carry several mentions of different people —
        check `is_me` to tell whether this line was one of them.

        Only iMessage carries mentions. On a received message this is populated when the
        sender was on iMessage; SMS and RCS have no way to mark a mention, so a message
        from an SMS or RCS participant arrives as plain text with `mentions` null, even in
        a group where other participants are on iMessage.

        - `handle: string`

          Address of the mentioned participant, exactly as the device recorded it — an E.164
          phone number or an email address.

        - `is_me: boolean`

          Whether the mentioned participant is this line.

        - `range: Array<number>`

          Character range `[start, end)` in `value` highlighted as this mention.
          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

      - `text_decorations?: Array<TextDecoration> | null`

        Text decorations applied to character ranges in the value

        - `range: Array<number>`

          Character range `[start, end)` in the `value` string where the decoration applies.
          `start` is inclusive, `end` is exclusive.
          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

        - `animation?: "big" | "small" | "shake" | 5 more`

          Animated text effect to apply. Mutually exclusive with `style`.

          - `"big"`

          - `"small"`

          - `"shake"`

          - `"nod"`

          - `"explode"`

          - `"ripple"`

          - `"bloom"`

          - `"jitter"`

        - `style?: "bold" | "italic" | "strikethrough" | "underline"`

          Text style to apply. Mutually exclusive with `animation`.

          - `"bold"`

          - `"italic"`

          - `"strikethrough"`

          - `"underline"`

    - `MediaPartResponse`

      A media attachment part

      - `id: string`

        Unique attachment identifier

      - `filename: string`

        Original filename

      - `mime_type: string`

        MIME type of the file

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `size_bytes: number`

        File size in bytes

      - `type: "media"`

        Indicates this is a media attachment part

        - `"media"`

      - `url: string`

        Presigned URL for downloading the attachment (expires in 1 hour).

    - `LinkPartResponse`

      A rich link preview part

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `type: "link"`

        Indicates this is a rich link preview part

        - `"link"`

      - `value: string`

        The URL

    - `IMessageAppPartResponse`

      An iMessage app card part.

      - `app: App`

        Identifies the iMessage app (Messages app extension) that backs the card.

        - `bundle_id: string`

          Bundle identifier of the Messages app extension. Must not contain `:`.

        - `name: string`

          Display name of the app, shown by Messages' fallback UI.

        - `team_id: string`

          The app's 10-character uppercase alphanumeric team identifier.

        - `app_store_id?: number`

          The owning app's App Store id (optional). When set, recipients without the iMessage app
          installed see a "Get the app" affordance.

      - `layout: Layout`

        Visible layout of the card. At least one of
        `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
        set, otherwise the card renders as an empty bubble.

        `image_url` displays a preview image at the top of the card. The image renders on the
        recipient's card whether or not they have your app installed. The small icon beside the
        caption is the app's own icon and is not settable here.

        `* Note - requires a trusted chat w/ inbound activity`

        `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
        beneath it). They only appear when `image_url` is set — without an image there is nothing to
        overlay — so setting either without `image_url` is rejected.

        - `caption?: string`

          Primary label, top-left and bold.

        - `image_subtitle?: string`

          Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

        - `image_title?: string`

          Bold text overlaid on the card image. Requires `image_url` (rejected without it).

        - `image_url?: string`

          URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

        - `subcaption?: string`

          Secondary label, below `caption` on the left.

        - `trailing_caption?: string`

          Label shown top-right.

        - `trailing_subcaption?: string`

          Label shown below `trailing_caption`, on the right.

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `type: "imessage_app"`

        Indicates this is an iMessage app card part.

        - `"imessage_app"`

      - `url: string`

        The URL delivered to the iMessage app on tap.

      - `fallback_text?: string | null`

        Fallback text for surfaces that cannot render the card.

    - `AppClipPartResponse`

      An App Clip card part

      - `reactions: Array<Reaction> | null`

        Reactions on this message part

        - `handle: ChatHandle`

        - `is_me: boolean`

          Whether this reaction is from the current user

        - `type: ReactionType`

          Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
          Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
          Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

        - `id?: string`

          Identifier for this reaction. Pass it to
          `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

          Stickers placed before this API shipped can be read but not moved: the
          device-side reference needed to reposition them was never recorded, so
          `PATCH` returns 404 for those.

        - `custom_emoji?: string | null`

          Custom emoji if type is "custom", null otherwise

        - `sticker?: Sticker | null`

          Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `type: "app_clip"`

        Indicates this is an App Clip card part

        - `"app_clip"`

      - `value: string`

        The App Clip link the card opens

      - `description?: string`

        The card's summary line, composed by Linq from the App Clip page

      - `image_url?: string`

        The card's preview image

      - `title?: string`

        The card's headline, composed by Linq from the App Clip page

  - `sent_at: string | null`

    When the message was actually sent (null if still queued)

  - `delivered_at?: string | null`

    When the message was delivered

  - `effect?: MessageEffect | null`

    iMessage effect applied to a message (screen or bubble effect)

    - `name?: string`

      Name of the effect. Common values:

      - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
      - Bubble effects: slam, loud, gentle, invisible

    - `type?: "screen" | "bubble"`

      Type of effect

      - `"screen"`

      - `"bubble"`

  - `from_handle?: ChatHandle | null`

    The sender of this message as a full handle object

  - `preferred_service?: ServiceType | null`

    Messaging service type

  - `reply_to?: ReplyTo | null`

    Indicates this message is a threaded reply to another message

    - `message_id: string`

      The ID of the message to reply to

    - `part_index?: number`

      The specific message part to reply to (0-based index).
      Defaults to 0 (first part) if not provided.
      Use this when replying to a specific part of a multipart message.

  - `service?: ServiceType | null`

    Messaging service type

### Message Send Response

- `MessageSendResponse`

  Response for sending a message to a chat

  - `chat_id: string`

    Unique identifier of the chat this message was sent to

  - `message: SentMessage`

    A message that was sent (used in CreateChat and SendMessage responses)

    - `id: string`

      Message identifier (UUID)

    - `created_at: string`

      When the message was created

    - `delivery_status: "pending" | "queued" | "sent" | 4 more`

      Current delivery status of a message

      - `"pending"`

      - `"queued"`

      - `"sent"`

      - `"delivered"`

      - `"received"`

      - `"read"`

      - `"failed"`

    - `is_read: boolean`

      DEPRECATED: Use `delivery_status == "read"` instead. Whether the message has been read.

    - `parts: Array<TextPartResponse | MediaPartResponse | LinkPartResponse | 2 more>`

      Message parts in order (text, media, and link)

      - `TextPartResponse`

        A text message part

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

            - `id: string`

              Unique identifier for this handle

            - `handle: string`

              Phone number (E.164) or email address of the participant

            - `joined_at: string`

              When this participant joined the chat

            - `service: ServiceType`

              Messaging service type

              - `"iMessage"`

              - `"SMS"`

              - `"RCS"`

            - `is_me?: boolean | null`

              Whether this handle belongs to the sender (your phone number)

            - `left_at?: string | null`

              When they left (if applicable)

            - `status?: "active" | "left" | "removed" | null`

              Participant status

              - `"active"`

              - `"left"`

              - `"removed"`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `"love"`

            - `"like"`

            - `"dislike"`

            - `"laugh"`

            - `"emphasize"`

            - `"question"`

            - `"custom"`

            - `"sticker"`

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

            - `file_name?: string`

              Filename of the sticker

            - `height?: number`

              Sticker image height in pixels

            - `mime_type?: string`

              MIME type of the sticker image

            - `url?: string`

              Presigned URL for downloading the sticker image (expires in 1 hour).

            - `width?: number`

              Sticker image width in pixels

        - `type: "text"`

          Indicates this is a text message part

          - `"text"`

        - `value: string`

          The text content

        - `mention?: string | null`

          DEPRECATED: Use `mentions` instead. Handle (E.164 phone number or Apple ID email)
          of the **first** mention on this part. A part may carry several mentions; this
          field shows only the first in `value` order, so it cannot be used to determine
          whether a given participant was mentioned. `null` when the part carries no mention.

        - `mention_range?: Array<number> | null`

          DEPRECATED: Use `mentions[].range` instead. Character range `[start, end)` in
          `value` highlighted as the **first** mention only. `null` when the range was
          omitted (the whole `value` is highlighted) or the part carries no mention.
          *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

        - `mentions?: Array<Mention> | null`

          Every mention on this part, in the order they appear in `value`. `null` when the
          part carries no mention. A part can carry several mentions of different people —
          check `is_me` to tell whether this line was one of them.

          Only iMessage carries mentions. On a received message this is populated when the
          sender was on iMessage; SMS and RCS have no way to mark a mention, so a message
          from an SMS or RCS participant arrives as plain text with `mentions` null, even in
          a group where other participants are on iMessage.

          - `handle: string`

            Address of the mentioned participant, exactly as the device recorded it — an E.164
            phone number or an email address.

          - `is_me: boolean`

            Whether the mentioned participant is this line.

          - `range: Array<number>`

            Character range `[start, end)` in `value` highlighted as this mention.
            *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

        - `text_decorations?: Array<TextDecoration> | null`

          Text decorations applied to character ranges in the value

          - `range: Array<number>`

            Character range `[start, end)` in the `value` string where the decoration applies.
            `start` is inclusive, `end` is exclusive.
            *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `animation?: "big" | "small" | "shake" | 5 more`

            Animated text effect to apply. Mutually exclusive with `style`.

            - `"big"`

            - `"small"`

            - `"shake"`

            - `"nod"`

            - `"explode"`

            - `"ripple"`

            - `"bloom"`

            - `"jitter"`

          - `style?: "bold" | "italic" | "strikethrough" | "underline"`

            Text style to apply. Mutually exclusive with `animation`.

            - `"bold"`

            - `"italic"`

            - `"strikethrough"`

            - `"underline"`

      - `MediaPartResponse`

        A media attachment part

        - `id: string`

          Unique attachment identifier

        - `filename: string`

          Original filename

        - `mime_type: string`

          MIME type of the file

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

        - `size_bytes: number`

          File size in bytes

        - `type: "media"`

          Indicates this is a media attachment part

          - `"media"`

        - `url: string`

          Presigned URL for downloading the attachment (expires in 1 hour).

      - `LinkPartResponse`

        A rich link preview part

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

        - `type: "link"`

          Indicates this is a rich link preview part

          - `"link"`

        - `value: string`

          The URL

      - `IMessageAppPartResponse`

        An iMessage app card part.

        - `app: App`

          Identifies the iMessage app (Messages app extension) that backs the card.

          - `bundle_id: string`

            Bundle identifier of the Messages app extension. Must not contain `:`.

          - `name: string`

            Display name of the app, shown by Messages' fallback UI.

          - `team_id: string`

            The app's 10-character uppercase alphanumeric team identifier.

          - `app_store_id?: number`

            The owning app's App Store id (optional). When set, recipients without the iMessage app
            installed see a "Get the app" affordance.

        - `layout: Layout`

          Visible layout of the card. At least one of
          `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
          set, otherwise the card renders as an empty bubble.

          `image_url` displays a preview image at the top of the card. The image renders on the
          recipient's card whether or not they have your app installed. The small icon beside the
          caption is the app's own icon and is not settable here.

          `* Note - requires a trusted chat w/ inbound activity`

          `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
          beneath it). They only appear when `image_url` is set — without an image there is nothing to
          overlay — so setting either without `image_url` is rejected.

          - `caption?: string`

            Primary label, top-left and bold.

          - `image_subtitle?: string`

            Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

          - `image_title?: string`

            Bold text overlaid on the card image. Requires `image_url` (rejected without it).

          - `image_url?: string`

            URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

          - `subcaption?: string`

            Secondary label, below `caption` on the left.

          - `trailing_caption?: string`

            Label shown top-right.

          - `trailing_subcaption?: string`

            Label shown below `trailing_caption`, on the right.

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

        - `type: "imessage_app"`

          Indicates this is an iMessage app card part.

          - `"imessage_app"`

        - `url: string`

          The URL delivered to the iMessage app on tap.

        - `fallback_text?: string | null`

          Fallback text for surfaces that cannot render the card.

      - `AppClipPartResponse`

        An App Clip card part

        - `reactions: Array<Reaction> | null`

          Reactions on this message part

          - `handle: ChatHandle`

          - `is_me: boolean`

            Whether this reaction is from the current user

          - `type: ReactionType`

            Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
            Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
            Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

          - `id?: string`

            Identifier for this reaction. Pass it to
            `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

            Stickers placed before this API shipped can be read but not moved: the
            device-side reference needed to reposition them was never recorded, so
            `PATCH` returns 404 for those.

          - `custom_emoji?: string | null`

            Custom emoji if type is "custom", null otherwise

          - `sticker?: Sticker | null`

            Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

        - `type: "app_clip"`

          Indicates this is an App Clip card part

          - `"app_clip"`

        - `value: string`

          The App Clip link the card opens

        - `description?: string`

          The card's summary line, composed by Linq from the App Clip page

        - `image_url?: string`

          The card's preview image

        - `title?: string`

          The card's headline, composed by Linq from the App Clip page

    - `sent_at: string | null`

      When the message was actually sent (null if still queued)

    - `delivered_at?: string | null`

      When the message was delivered

    - `effect?: MessageEffect | null`

      iMessage effect applied to a message (screen or bubble effect)

      - `name?: string`

        Name of the effect. Common values:

        - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
        - Bubble effects: slam, loud, gentle, invisible

      - `type?: "screen" | "bubble"`

        Type of effect

        - `"screen"`

        - `"bubble"`

    - `from_handle?: ChatHandle | null`

      The sender of this message as a full handle object

    - `preferred_service?: ServiceType | null`

      Messaging service type

    - `reply_to?: ReplyTo | null`

      Indicates this message is a threaded reply to another message

      - `message_id: string`

        The ID of the message to reply to

      - `part_index?: number`

        The specific message part to reply to (0-based index).
        Defaults to 0 (first part) if not provided.
        Use this when replying to a specific part of a multipart message.

    - `service?: ServiceType | null`

      Messaging service type

# Location

## Request location sharing

`client.chats.location.request(stringchatID, RequestOptionsoptions?): LocationRequestResponse`

**post** `/v3/chats/{chatId}/location/request`

Request a contact in a chat to share their location. They receive an iMessage
prompt and must accept before any location is available; once they do, read their
location coordinates with `GET /v3/chats/{chatId}/location`.

The request is delivered asynchronously. The endpoint returns immediately with
`{ "success": true, "message": "Location request sent" }` and does not return
coordinates.

Rejected with `409` if the recipient is already sharing — read their
location with `GET /v3/chats/{chatId}/location` instead of re-requesting.

Rate limited per chat, since each request prompts the recipient's device.
Exceeding it returns `429` with a `Retry-After` header.

Location requests only work in **1:1 iMessage chats** (Apple limitation):

- Group chats (any service) return `409` with code `2016`
  (`GroupChatNotSupported`).
- 1:1 SMS and RCS chats return `409` with code `2017`
  (`ChatServiceNotSupported`).

### Parameters

- `chatID: string`

### Returns

- `LocationRequestResponse`

  - `message: string`

  - `success: boolean`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const locationRequestResponse = await client.chats.location.request(
  '975d0776-bd17-4273-8337-f346b4c661b0',
);

console.log(locationRequestResponse.message);
```

#### Response

```json
{
  "success": true,
  "message": "Location request sent"
}
```

## Get location data

`client.chats.location.retrieve(stringchatID, RequestOptionsoptions?): GetChatLocationResponse`

**get** `/v3/chats/{chatId}/location`

Retrieve the current location for contacts sharing with you in a chat.

The response is wrapped in the standard `{ "success": true, "data": ... }` envelope —
the body is **not** a bare GeoJSON document. `data` is a
[GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) `FeatureCollection` with a
`Feature` for each participant actively sharing their location.

Works for both 1:1 and group chats. In group chats, `data.features` contains a separate
feature for each participant who is sharing. Each feature's `properties.handle` identifies the user.

A participant appears as soon as their first position arrives, typically
within a second or two of sharing starting.

Returns an empty `data.features` array if no one is sharing or no location data is
available yet. If sharing started but this stays empty, see the **Location Sharing**
overview.

Poll this endpoint to track a moving contact. `properties.updated_at`
reflects when each participant's location was last updated. There is no
coordinate-update webhook. See the **Location Sharing** overview for polling
guidance.

### Parameters

- `chatID: string`

### Returns

- `GetChatLocationResponse`

  - `data: Data`

    - `features: Array<Feature>`

      - `geometry: Geometry`

        - `coordinates: Array<number>`

          [longitude, latitude]

        - `type: "Point"`

          - `"Point"`

      - `properties: Properties`

        - `handle: string`

          Phone number or email of the person sharing their location

        - `address?: string`

          Full street address

        - `locality?: string`

          City or locality name

        - `updated_at?: string`

          When the location was last updated

      - `type: "Feature"`

        - `"Feature"`

    - `type: "FeatureCollection"`

      - `"FeatureCollection"`

  - `success: boolean`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const getChatLocationResponse = await client.chats.location.retrieve(
  '975d0776-bd17-4273-8337-f346b4c661b0',
);

console.log(getChatLocationResponse.data);
```

#### Response

```json
{
  "error": {
    "status": 400,
    "code": 1002,
    "message": "Phone number must be in E.164 format",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/1xxx/1002/"
  },
  "success": false
}
```

## Stop location sharing

`client.chats.location.stop(stringchatID, LocationStopParamsbody, RequestOptionsoptions?): StopChatLocationSharingResponse`

**delete** `/v3/chats/{chatId}/location`

End the location share a contact started with you, as though they had stopped it
themselves. Their device stops listing you as someone they share with, so they can
start a fresh share cleanly.

Use this to recover when a share has gone stale — coordinates that stop advancing, or
a share you believe has ended but is still reported as active. Without it the only
remedy is asking the contact to stop and re-share, which is confusing for them because
their phone still shows everything as working.

This is not reversible from the API. Sharing can only resume when the contact starts a
new share, so prompt them to re-share afterwards. Request a new one with
`POST /v3/chats/{chatId}/location/request`.

Apple keeps one location-sharing relationship per person rather than per chat, so this
ends that contact's share everywhere, not only in this chat.

`handle` names whose share to end, and is always required — a group chat can have several
people sharing, and this is not an operation to infer a target for.

**This returns `202`, not `200`.** The removal happens on the device that holds the
sharing relationship, so a success here means the request was accepted, not that
sharing has ended. Wait for the `location.sharing.stopped` webhook to confirm it —
that webhook is what tells you the contact's device has actually let go.

Returns `404` if the contact is not currently sharing.

### Parameters

- `chatID: string`

- `body: LocationStopParams`

  - `handle: string`

    Phone number (E.164 format) or email address of the contact whose share to end

### Returns

- `StopChatLocationSharingResponse`

  - `message: string`

  - `success: boolean`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const stopChatLocationSharingResponse = await client.chats.location.stop(
  '975d0776-bd17-4273-8337-f346b4c661b0',
  { handle: '+15551234567' },
);

console.log(stopChatLocationSharingResponse.message);
```

#### Response

```json
{
  "success": true,
  "message": "Location sharing stop requested"
}
```

## Domain Types

### Get Chat Location Response

- `GetChatLocationResponse`

  - `data: Data`

    - `features: Array<Feature>`

      - `geometry: Geometry`

        - `coordinates: Array<number>`

          [longitude, latitude]

        - `type: "Point"`

          - `"Point"`

      - `properties: Properties`

        - `handle: string`

          Phone number or email of the person sharing their location

        - `address?: string`

          Full street address

        - `locality?: string`

          City or locality name

        - `updated_at?: string`

          When the location was last updated

      - `type: "Feature"`

        - `"Feature"`

    - `type: "FeatureCollection"`

      - `"FeatureCollection"`

  - `success: boolean`

### Location Request Response

- `LocationRequestResponse`

  - `message: string`

  - `success: boolean`

### Stop Chat Location Sharing Response

- `StopChatLocationSharingResponse`

  - `message: string`

  - `success: boolean`

# Polls

## Create and send a poll in a chat

`client.chats.polls.create(stringchatID, PollCreateParamsbody, RequestOptionsoptions?): PollEnvelope`

**post** `/v3/chats/{chatId}/polls`

Create an iMessage poll in an existing chat and send it. Polls are iMessage-only.

The chat must already exist — **a poll cannot be the first message of a
new chat** (use `POST /v3/chats` for that). Options are **add-only and immutable**: you
can add options later via `POST /v3/messages/{messageId}/poll/options`, but never edit
or remove them.

### Parameters

- `chatID: string`

- `body: PollCreateParams`

  - `poll: Poll`

    Poll content to create. A poll needs at least two options. Options are add-only and
    immutable — there is no title/question (send that as a normal text message).

    - `options: Array<Option>`

      - `text: string`

    - `idempotency_key?: string`

      Optional key to deduplicate the poll creation.

### Returns

- `PollEnvelope`

  Message-level envelope returned by every poll endpoint.

  - `chat_id: string`

  - `created_at: string`

  - `message_id: string`

    The poll-definition message's ID — reference this poll by it.

  - `poll: Poll`

    Poll content — options and the aggregate voter count.

    - `options: Array<Option>`

      - `can_be_edited: boolean`

      - `creator_handle: ChatHandle`

        The participant who added this option (poll creator for the initial options; whoever added later ones).

        - `id: string`

          Unique identifier for this handle

        - `handle: string`

          Phone number (E.164) or email address of the participant

        - `joined_at: string`

          When this participant joined the chat

        - `service: ServiceType`

          Messaging service type

          - `"iMessage"`

          - `"SMS"`

          - `"RCS"`

        - `is_me?: boolean | null`

          Whether this handle belongs to the sender (your phone number)

        - `left_at?: string | null`

          When they left (if applicable)

        - `status?: "active" | "left" | "removed" | null`

          Participant status

          - `"active"`

          - `"left"`

          - `"removed"`

      - `option_id: string`

      - `text: string`

      - `voters: Array<Voter>`

        Participants who voted for this option (vote_count = voters.length).

        - `handle: string`

        - `voted_at: string`

    - `total_voters: number`

      Distinct participants across the whole poll (a voter picking two options counts once).

  - `reactions: Array<Reaction>`

    Tapbacks/stickers on the whole poll (message part 0).

    - `handle: ChatHandle`

    - `is_me: boolean`

      Whether this reaction is from the current user

    - `type: ReactionType`

      Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
      Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
      Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

      - `"love"`

      - `"like"`

      - `"dislike"`

      - `"laugh"`

      - `"emphasize"`

      - `"question"`

      - `"custom"`

      - `"sticker"`

    - `id?: string`

      Identifier for this reaction. Pass it to
      `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

      Stickers placed before this API shipped can be read but not moved: the
      device-side reference needed to reposition them was never recorded, so
      `PATCH` returns 404 for those.

    - `custom_emoji?: string | null`

      Custom emoji if type is "custom", null otherwise

    - `sticker?: Sticker | null`

      Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `file_name?: string`

        Filename of the sticker

      - `height?: number`

        Sticker image height in pixels

      - `mime_type?: string`

        MIME type of the sticker image

      - `url?: string`

        Presigned URL for downloading the sticker image (expires in 1 hour).

      - `width?: number`

        Sticker image width in pixels

  - `updated_at: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

const pollEnvelope = await client.chats.polls.create('550e8400-e29b-41d4-a716-446655440000', {
  poll: { options: [{ text: 'Tacos' }, { text: 'Sushi' }], idempotency_key: 'poll-abc123' },
});

console.log(pollEnvelope.chat_id);
```

#### Response

```json
{
  "chat_id": "550e8400-e29b-41d4-a716-446655440000",
  "created_at": "2019-12-27T18:11:19.117Z",
  "message_id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
  "poll": {
    "options": [
      {
        "can_be_edited": true,
        "creator_handle": {
          "id": "550e8400-e29b-41d4-a716-446655440000",
          "handle": "+15551234567",
          "joined_at": "2025-05-21T15:30:00.000-05:00",
          "service": "iMessage",
          "is_me": false,
          "left_at": "2019-12-27T18:11:19.117Z",
          "status": "active"
        },
        "option_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
        "text": "Tacos",
        "voters": [
          {
            "handle": "+14155559876",
            "voted_at": "2019-12-27T18:11:19.117Z"
          }
        ]
      }
    ],
    "total_voters": 0
  },
  "reactions": [
    {
      "handle": {
        "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
        "handle": "+15551234567",
        "joined_at": "2025-05-21T15:30:00.000-05:00",
        "service": "iMessage",
        "is_me": false,
        "left_at": "2019-12-27T18:11:19.117Z",
        "status": "active"
      },
      "is_me": false,
      "type": "love",
      "id": "9f8b1c2d-3e4f-5061-7283-94a5b6c7d8e9",
      "custom_emoji": null,
      "sticker": {
        "file_name": "sticker.png",
        "height": 420,
        "mime_type": "image/png",
        "url": "https://cdn.linqapp.com/attachments/a1b2c3d4/sticker.png?signature=...",
        "width": 420
      }
    }
  ],
  "updated_at": "2019-12-27T18:11:19.117Z"
}
```

## Domain Types

### Poll

- `Poll`

  Poll content — options and the aggregate voter count.

  - `options: Array<Option>`

    - `can_be_edited: boolean`

    - `creator_handle: ChatHandle`

      The participant who added this option (poll creator for the initial options; whoever added later ones).

      - `id: string`

        Unique identifier for this handle

      - `handle: string`

        Phone number (E.164) or email address of the participant

      - `joined_at: string`

        When this participant joined the chat

      - `service: ServiceType`

        Messaging service type

        - `"iMessage"`

        - `"SMS"`

        - `"RCS"`

      - `is_me?: boolean | null`

        Whether this handle belongs to the sender (your phone number)

      - `left_at?: string | null`

        When they left (if applicable)

      - `status?: "active" | "left" | "removed" | null`

        Participant status

        - `"active"`

        - `"left"`

        - `"removed"`

    - `option_id: string`

    - `text: string`

    - `voters: Array<Voter>`

      Participants who voted for this option (vote_count = voters.length).

      - `handle: string`

      - `voted_at: string`

  - `total_voters: number`

    Distinct participants across the whole poll (a voter picking two options counts once).

### Poll Envelope

- `PollEnvelope`

  Message-level envelope returned by every poll endpoint.

  - `chat_id: string`

  - `created_at: string`

  - `message_id: string`

    The poll-definition message's ID — reference this poll by it.

  - `poll: Poll`

    Poll content — options and the aggregate voter count.

    - `options: Array<Option>`

      - `can_be_edited: boolean`

      - `creator_handle: ChatHandle`

        The participant who added this option (poll creator for the initial options; whoever added later ones).

        - `id: string`

          Unique identifier for this handle

        - `handle: string`

          Phone number (E.164) or email address of the participant

        - `joined_at: string`

          When this participant joined the chat

        - `service: ServiceType`

          Messaging service type

          - `"iMessage"`

          - `"SMS"`

          - `"RCS"`

        - `is_me?: boolean | null`

          Whether this handle belongs to the sender (your phone number)

        - `left_at?: string | null`

          When they left (if applicable)

        - `status?: "active" | "left" | "removed" | null`

          Participant status

          - `"active"`

          - `"left"`

          - `"removed"`

      - `option_id: string`

      - `text: string`

      - `voters: Array<Voter>`

        Participants who voted for this option (vote_count = voters.length).

        - `handle: string`

        - `voted_at: string`

    - `total_voters: number`

      Distinct participants across the whole poll (a voter picking two options counts once).

  - `reactions: Array<Reaction>`

    Tapbacks/stickers on the whole poll (message part 0).

    - `handle: ChatHandle`

    - `is_me: boolean`

      Whether this reaction is from the current user

    - `type: ReactionType`

      Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
      Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
      Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

      - `"love"`

      - `"like"`

      - `"dislike"`

      - `"laugh"`

      - `"emphasize"`

      - `"question"`

      - `"custom"`

      - `"sticker"`

    - `id?: string`

      Identifier for this reaction. Pass it to
      `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

      Stickers placed before this API shipped can be read but not moved: the
      device-side reference needed to reposition them was never recorded, so
      `PATCH` returns 404 for those.

    - `custom_emoji?: string | null`

      Custom emoji if type is "custom", null otherwise

    - `sticker?: Sticker | null`

      Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

      - `file_name?: string`

        Filename of the sticker

      - `height?: number`

        Sticker image height in pixels

      - `mime_type?: string`

        MIME type of the sticker image

      - `url?: string`

        Presigned URL for downloading the sticker image (expires in 1 hour).

      - `width?: number`

        Sticker image width in pixels

  - `updated_at: string`

# Background

## Set chat background

`client.chats.background.set(stringchatID, BackgroundSetParamsbody, RequestOptionsoptions?): void`

**post** `/v3/chats/{chatId}/background`

Set the transcript background for a chat.

Provide one of: a **color** (a named preset or a custom 2-stop gradient),
a **dynamic** animated style, or a **photo** (by URL). The request is accepted
asynchronously; the terminal result arrives via the `chat.background_updated`
webhook on success, or `chat.background_update_failed` on failure.

**Group chats are supported.** Requests for RCS or SMS chats are accepted (`202`)
but no background is applied and no `chat.background_updated` webhook fires.

### Parameters

- `chatID: string`

- `body: BackgroundSetParams`

  - `type: "color" | "dynamic" | "photo"`

    The background family.

    - `"color"`

    - `"dynamic"`

    - `"photo"`

  - `image_url?: string`

    Photo: the image URL to embed in the background. Must be an absolute `https`
    URL pointing at an image (`.jpg`, `.png`, `.heic`, `.webp`), and the image is
    fetched and re-hosted on our CDN before the request is accepted — the same way
    `group_chat_icon` works. A URL we cannot fetch, or one that isn't an image, is
    rejected with a `400` (`5007`/`5006`) rather than failing later on the device.

    Example: `https://cdn.linqapp.com/u/bg.jpg`.

  - `shades?: Array<string>`

    Color with `variant: custom`: the two gradient stops as hex, top then bottom —
    e.g. `["#F2C4E1", "#F5A623"]`. Ignored for named color variants (they carry
    their own two colors).

  - `style?: "sky" | "water" | "aurora"`

    Dynamic: the animated style — `sky`, `water`, or `aurora`.

    - `"sky"`

    - `"water"`

    - `"aurora"`

  - `variant?: string`

    Color: a named swatch — `mango`, `ice`, `plum`, `deep_sea`, `green_apple`,
    `cherry`, `bubblegum`, `tangerine`, `magenta`, `lime`, `silver`, `carbon`,
    `stone` — or `custom` (supply `shades`). Omitting `variant` is equivalent to
    `custom`, so it still requires `shades`.

    Dynamic: required — the variant within the `style`. `sky`: `dusk`, `haze`,
    `sunset`, `clear`, `sunrise`, `dawn`. `water`: `light`, `dark`. `aurora`:
    `green`, `purple`, `pink`.

    An unrecognized value is rejected with `400`.

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

await client.chats.background.set('550e8400-e29b-41d4-a716-446655440000', {
  type: 'color',
  variant: 'mango',
});
```

#### Response

```json
{
  "error": {
    "status": 400,
    "code": 1002,
    "message": "Phone number must be in E.164 format",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/1xxx/1002/"
  },
  "success": false
}
```

## Remove chat background

`client.chats.background.remove(stringchatID, RequestOptionsoptions?): void`

**delete** `/v3/chats/{chatId}/background`

Remove the transcript background from a chat, resetting it to the default.

### Parameters

- `chatID: string`

### Example

```typescript
import LinqAPIV3 from '@linqapp/sdk';

const client = new LinqAPIV3({
  apiKey: process.env['LINQ_API_V3_API_KEY'], // This is the default and can be omitted
});

await client.chats.background.remove('550e8400-e29b-41d4-a716-446655440000');
```

#### Response

```json
{
  "error": {
    "status": 400,
    "code": 1002,
    "message": "Phone number must be in E.164 format",
    "doc_url": "https://docs.linqapp.com/channel/imessage/error/codes/1xxx/1002/"
  },
  "success": false
}
```
