# Chats

## Create a new chat

`client.Chats.New(ctx, body) (*ChatNewResponse, error)`

**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 ChatNewParams`

  - `From param.Field[string]`

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

  - `Message param.Field[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.

  - `To param.Field[[]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.

  - `OverrideOptout param.Field[bool]`

    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

- `type ChatNewResponse struct{…}`

  Response for creating a new chat with an initial message

  - `Chat ChatNewResponseChat`

    - `ID string`

      Unique identifier for the created chat (UUID)

    - `DisplayName string`

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

    - `Handles []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

      - `JoinedAt Time`

        When this participant joined the chat

      - `Service ServiceType`

        Messaging service type

        - `const ServiceTypeIMessage ServiceType = "iMessage"`

        - `const ServiceTypeSMS ServiceType = "SMS"`

        - `const ServiceTypeRCS ServiceType = "RCS"`

      - `IsMe bool`

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

      - `LeftAt Time`

        When they left (if applicable)

      - `Status ChatHandleStatus`

        Participant status

        - `const ChatHandleStatusActive ChatHandleStatus = "active"`

        - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

        - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

    - `HealthStatus ChatNewResponseChatHealthStatus`

      **[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.

      - `DocURL string`

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

      - `Status string`

        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.

        - `const ChatNewResponseChatHealthStatusStatusHealthy ChatNewResponseChatHealthStatusStatus = "HEALTHY"`

        - `const ChatNewResponseChatHealthStatusStatusAtRisk ChatNewResponseChatHealthStatusStatus = "AT_RISK"`

        - `const ChatNewResponseChatHealthStatusStatusCritical ChatNewResponseChatHealthStatusStatus = "CRITICAL"`

        - `const ChatNewResponseChatHealthStatusStatusOptedOut ChatNewResponseChatHealthStatusStatus = "OPTED_OUT"`

      - `UpdatedAt Time`

        When this status last changed.

    - `IsGroup bool`

      Whether this is a group chat

    - `Message SentMessage`

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

      - `ID string`

        Message identifier (UUID)

      - `CreatedAt Time`

        When the message was created

      - `DeliveryStatus SentMessageDeliveryStatus`

        Current delivery status of a message

        - `const SentMessageDeliveryStatusPending SentMessageDeliveryStatus = "pending"`

        - `const SentMessageDeliveryStatusQueued SentMessageDeliveryStatus = "queued"`

        - `const SentMessageDeliveryStatusSent SentMessageDeliveryStatus = "sent"`

        - `const SentMessageDeliveryStatusDelivered SentMessageDeliveryStatus = "delivered"`

        - `const SentMessageDeliveryStatusReceived SentMessageDeliveryStatus = "received"`

        - `const SentMessageDeliveryStatusRead SentMessageDeliveryStatus = "read"`

        - `const SentMessageDeliveryStatusFailed SentMessageDeliveryStatus = "failed"`

      - `IsRead bool`

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

      - `Parts []SentMessagePartUnion`

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

        - `type TextPartResponse struct{…}`

          A text message part

          - `Reactions []Reaction`

            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

              - `JoinedAt Time`

                When this participant joined the chat

              - `Service ServiceType`

                Messaging service type

              - `IsMe bool`

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

              - `LeftAt Time`

                When they left (if applicable)

              - `Status ChatHandleStatus`

                Participant status

            - `IsMe bool`

              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.

              - `const ReactionTypeLove ReactionType = "love"`

              - `const ReactionTypeLike ReactionType = "like"`

              - `const ReactionTypeDislike ReactionType = "dislike"`

              - `const ReactionTypeLaugh ReactionType = "laugh"`

              - `const ReactionTypeEmphasize ReactionType = "emphasize"`

              - `const ReactionTypeQuestion ReactionType = "question"`

              - `const ReactionTypeCustom ReactionType = "custom"`

              - `const ReactionTypeSticker ReactionType = "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.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

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

              - `FileName string`

                Filename of the sticker

              - `Height int64`

                Sticker image height in pixels

              - `MimeType string`

                MIME type of the sticker image

              - `URL string`

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

              - `Width int64`

                Sticker image width in pixels

          - `Type TextPartResponseType`

            Indicates this is a text message part

            - `const TextPartResponseTypeText TextPartResponseType = "text"`

          - `Value string`

            The text content

          - `Mention string`

            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.

          - `MentionRange []int64`

            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 []TextPartResponseMention`

            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.

            - `IsMe bool`

              Whether the mentioned participant is this line.

            - `Range []int64`

              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.*

          - `TextDecorations []TextDecoration`

            Text decorations applied to character ranges in the value

            - `Range []int64`

              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 TextDecorationAnimation`

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

              - `const TextDecorationAnimationBig TextDecorationAnimation = "big"`

              - `const TextDecorationAnimationSmall TextDecorationAnimation = "small"`

              - `const TextDecorationAnimationShake TextDecorationAnimation = "shake"`

              - `const TextDecorationAnimationNod TextDecorationAnimation = "nod"`

              - `const TextDecorationAnimationExplode TextDecorationAnimation = "explode"`

              - `const TextDecorationAnimationRipple TextDecorationAnimation = "ripple"`

              - `const TextDecorationAnimationBloom TextDecorationAnimation = "bloom"`

              - `const TextDecorationAnimationJitter TextDecorationAnimation = "jitter"`

            - `Style TextDecorationStyle`

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

              - `const TextDecorationStyleBold TextDecorationStyle = "bold"`

              - `const TextDecorationStyleItalic TextDecorationStyle = "italic"`

              - `const TextDecorationStyleStrikethrough TextDecorationStyle = "strikethrough"`

              - `const TextDecorationStyleUnderline TextDecorationStyle = "underline"`

        - `type MediaPartResponse struct{…}`

          A media attachment part

          - `ID string`

            Unique attachment identifier

          - `Filename string`

            Original filename

          - `MimeType string`

            MIME type of the file

          - `Reactions []Reaction`

            Reactions on this message part

            - `Handle ChatHandle`

            - `IsMe bool`

              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.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

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

          - `SizeBytes int64`

            File size in bytes

          - `Type MediaPartResponseType`

            Indicates this is a media attachment part

            - `const MediaPartResponseTypeMedia MediaPartResponseType = "media"`

          - `URL string`

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

        - `type LinkPartResponse struct{…}`

          A rich link preview part

          - `Reactions []Reaction`

            Reactions on this message part

            - `Handle ChatHandle`

            - `IsMe bool`

              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.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

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

          - `Type LinkPartResponseType`

            Indicates this is a rich link preview part

            - `const LinkPartResponseTypeLink LinkPartResponseType = "link"`

          - `Value string`

            The URL

        - `type SentMessagePartIMessageAppPartResponse struct{…}`

          An iMessage app card part.

          - `App SentMessagePartIMessageAppPartResponseApp`

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

            - `BundleID string`

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

            - `Name string`

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

            - `TeamID string`

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

            - `AppStoreID int64`

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

          - `Layout SentMessagePartIMessageAppPartResponseLayout`

            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.

            - `ImageSubtitle string`

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

            - `ImageTitle string`

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

            - `ImageURL 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.

            - `TrailingCaption string`

              Label shown top-right.

            - `TrailingSubcaption string`

              Label shown below `trailing_caption`, on the right.

          - `Reactions []Reaction`

            Reactions on this message part

            - `Handle ChatHandle`

            - `IsMe bool`

              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.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

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

          - `Type string`

            Indicates this is an iMessage app card part.

            - `const SentMessagePartIMessageAppPartResponseTypeIMessageApp SentMessagePartIMessageAppPartResponseType = "imessage_app"`

          - `URL string`

            The URL delivered to the iMessage app on tap.

          - `FallbackText string`

            Fallback text for surfaces that cannot render the card.

        - `type SentMessagePartAppClipPartResponse struct{…}`

          An App Clip card part

          - `Reactions []Reaction`

            Reactions on this message part

            - `Handle ChatHandle`

            - `IsMe bool`

              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.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

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

          - `Type string`

            Indicates this is an App Clip card part

            - `const SentMessagePartAppClipPartResponseTypeAppClip SentMessagePartAppClipPartResponseType = "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

          - `ImageURL string`

            The card's preview image

          - `Title string`

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

      - `SentAt Time`

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

      - `DeliveredAt Time`

        When the message was delivered

      - `Effect MessageEffect`

        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 MessageEffectType`

          Type of effect

          - `const MessageEffectTypeScreen MessageEffectType = "screen"`

          - `const MessageEffectTypeBubble MessageEffectType = "bubble"`

      - `FromHandle ChatHandle`

        The sender of this message as a full handle object

      - `PreferredService ServiceType`

        Messaging service type

      - `ReplyTo ReplyTo`

        Indicates this message is a threaded reply to another message

        - `MessageID string`

          The ID of the message to reply to

        - `PartIndex int64`

          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`

        Messaging service type

    - `Service ServiceType`

      Messaging service type

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  chat, err := client.Chats.New(context.TODO(), linqgo.ChatNewParams{
    From: "+12052535597",
    Message: linqgo.MessageContentParam{
      Parts: []linqgo.MessageContentPartUnionParam{linqgo.MessageContentPartUnionParam{
        OfText: &linqgo.TextPartParam{
          Type: linqgo.TextPartTypeText,
          Value: "Hello! How can I help you today?",
        },
      }},
    },
    To: []string{"+12052532136"},
  })
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", 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(ctx, query) (*ListChatsPagination[Chat], error)`

**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 param.Field[string]`

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

  - `From param.Field[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 param.Field[int64]`

    Maximum number of chats to return per page

  - `To param.Field[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

- `type Chat struct{…}`

  - `ID string`

    Unique identifier for the chat

  - `CreatedAt Time`

    When the chat was created

  - `DisplayName string`

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

  - `Handles []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

    - `JoinedAt Time`

      When this participant joined the chat

    - `Service ServiceType`

      Messaging service type

      - `const ServiceTypeIMessage ServiceType = "iMessage"`

      - `const ServiceTypeSMS ServiceType = "SMS"`

      - `const ServiceTypeRCS ServiceType = "RCS"`

    - `IsMe bool`

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

    - `LeftAt Time`

      When they left (if applicable)

    - `Status ChatHandleStatus`

      Participant status

      - `const ChatHandleStatusActive ChatHandleStatus = "active"`

      - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

      - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

  - `HealthStatus ChatHealthStatus`

    **[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.

    - `DocURL string`

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

    - `Status string`

      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.

      - `const ChatHealthStatusStatusHealthy ChatHealthStatusStatus = "HEALTHY"`

      - `const ChatHealthStatusStatusAtRisk ChatHealthStatusStatus = "AT_RISK"`

      - `const ChatHealthStatusStatusCritical ChatHealthStatusStatus = "CRITICAL"`

      - `const ChatHealthStatusStatusOptedOut ChatHealthStatusStatus = "OPTED_OUT"`

    - `UpdatedAt Time`

      When this status last changed.

  - `IsArchived bool`

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

  - `IsGroup bool`

    Whether this is a group chat

  - `UpdatedAt Time`

    When the chat was last updated

  - `GroupChatIcon string`

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

  - `Service ServiceType`

    Messaging service type

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  page, err := client.Chats.ListChats(context.TODO(), linqgo.ChatListChatsParams{

  })
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", page)
}
```

#### 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.Get(ctx, chatID) (*Chat, error)`

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

Retrieve a chat by its unique identifier.

### Parameters

- `chatID string`

### Returns

- `type Chat struct{…}`

  - `ID string`

    Unique identifier for the chat

  - `CreatedAt Time`

    When the chat was created

  - `DisplayName string`

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

  - `Handles []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

    - `JoinedAt Time`

      When this participant joined the chat

    - `Service ServiceType`

      Messaging service type

      - `const ServiceTypeIMessage ServiceType = "iMessage"`

      - `const ServiceTypeSMS ServiceType = "SMS"`

      - `const ServiceTypeRCS ServiceType = "RCS"`

    - `IsMe bool`

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

    - `LeftAt Time`

      When they left (if applicable)

    - `Status ChatHandleStatus`

      Participant status

      - `const ChatHandleStatusActive ChatHandleStatus = "active"`

      - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

      - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

  - `HealthStatus ChatHealthStatus`

    **[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.

    - `DocURL string`

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

    - `Status string`

      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.

      - `const ChatHealthStatusStatusHealthy ChatHealthStatusStatus = "HEALTHY"`

      - `const ChatHealthStatusStatusAtRisk ChatHealthStatusStatus = "AT_RISK"`

      - `const ChatHealthStatusStatusCritical ChatHealthStatusStatus = "CRITICAL"`

      - `const ChatHealthStatusStatusOptedOut ChatHealthStatusStatus = "OPTED_OUT"`

    - `UpdatedAt Time`

      When this status last changed.

  - `IsArchived bool`

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

  - `IsGroup bool`

    Whether this is a group chat

  - `UpdatedAt Time`

    When the chat was last updated

  - `GroupChatIcon string`

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

  - `Service ServiceType`

    Messaging service type

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  chat, err := client.Chats.Get(context.TODO(), "550e8400-e29b-41d4-a716-446655440000")
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", 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(ctx, chatID, body) (*ChatUpdateResponse, error)`

**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`

  - `DisplayName param.Field[string]`

    New display name for the chat (group chats only)

  - `GroupChatIcon param.Field[string]`

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

### Returns

- `type ChatUpdateResponse struct{…}`

  - `ChatID string`

  - `Status string`

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  chat, err := client.Chats.Update(
    context.TODO(),
    "550e8400-e29b-41d4-a716-446655440000",
    linqgo.ChatUpdateParams{
      DisplayName: linqgo.String("Team Discussion"),
    },
  )
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", chat.ChatID)
}
```

#### Response

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

## Mark chat as read

`client.Chats.MarkAsRead(ctx, chatID) error`

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

Mark all messages in a chat as read.

### Parameters

- `chatID string`

### Example

```go
package main

import (
  "context"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  err := client.Chats.MarkAsRead(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")
  if err != nil {
    panic(err.Error())
  }
}
```

#### 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(ctx, chatID) (*ChatLeaveChatResponse, error)`

**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

- `type ChatLeaveChatResponse struct{…}`

  - `Message string`

  - `Status string`

  - `TraceID string`

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  response, err := client.Chats.LeaveChat(context.TODO(), "550e8400-e29b-41d4-a716-446655440000")
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", response.TraceID)
}
```

#### Response

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

## Share your contact card with a chat

`client.Chats.ShareContactCard(ctx, chatID) error`

**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

```go
package main

import (
  "context"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  err := client.Chats.ShareContactCard(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")
  if err != nil {
    panic(err.Error())
  }
}
```

#### 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(ctx, chatID, body) (*ChatSendVoicememoResponse, error)`

**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`

  - `AttachmentID param.Field[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.

  - `OverrideOptout param.Field[bool]`

    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.

  - `VoiceMemoURL param.Field[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

- `type ChatSendVoicememoResponse struct{…}`

  Response for sending a voice memo to a chat

  - `VoiceMemo ChatSendVoicememoResponseVoiceMemo`

    - `ID string`

      Message identifier

    - `Chat ChatSendVoicememoResponseVoiceMemoChat`

      - `ID string`

        Chat identifier

      - `Handles []ChatHandle`

        Chat participants

        - `ID string`

          Unique identifier for this handle

        - `Handle string`

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

        - `JoinedAt Time`

          When this participant joined the chat

        - `Service ServiceType`

          Messaging service type

          - `const ServiceTypeIMessage ServiceType = "iMessage"`

          - `const ServiceTypeSMS ServiceType = "SMS"`

          - `const ServiceTypeRCS ServiceType = "RCS"`

        - `IsMe bool`

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

        - `LeftAt Time`

          When they left (if applicable)

        - `Status ChatHandleStatus`

          Participant status

          - `const ChatHandleStatusActive ChatHandleStatus = "active"`

          - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

          - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

      - `IsActive bool`

        Whether the chat is active

      - `IsGroup bool`

        Whether this is a group chat

      - `Service ServiceType`

        Messaging service type

    - `CreatedAt Time`

      When the voice memo was created

    - `From string`

      Sender phone number

    - `Status string`

      Current delivery status

    - `To []string`

      Recipient handles (phone numbers or email addresses)

    - `VoiceMemo ChatSendVoicememoResponseVoiceMemoVoiceMemo`

      - `ID string`

        Attachment identifier

      - `Filename string`

        Original filename

      - `MimeType string`

        Audio MIME type

      - `SizeBytes int64`

        File size in bytes

      - `URL string`

        CDN URL for downloading the voice memo

      - `DurationMs int64`

        Duration in milliseconds

    - `Service ServiceType`

      Messaging service type

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  response, err := client.Chats.SendVoicememo(
    context.TODO(),
    "f19ee7b8-8533-4c5c-83ec-4ef8d6d1ddbd",
    linqgo.ChatSendVoicememoParams{
      VoiceMemoURL: linqgo.String("https://example.com/voice-memo.m4a"),
    },
  )
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", response.VoiceMemo)
}
```

#### 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

- `type Chat struct{…}`

  - `ID string`

    Unique identifier for the chat

  - `CreatedAt Time`

    When the chat was created

  - `DisplayName string`

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

  - `Handles []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

    - `JoinedAt Time`

      When this participant joined the chat

    - `Service ServiceType`

      Messaging service type

      - `const ServiceTypeIMessage ServiceType = "iMessage"`

      - `const ServiceTypeSMS ServiceType = "SMS"`

      - `const ServiceTypeRCS ServiceType = "RCS"`

    - `IsMe bool`

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

    - `LeftAt Time`

      When they left (if applicable)

    - `Status ChatHandleStatus`

      Participant status

      - `const ChatHandleStatusActive ChatHandleStatus = "active"`

      - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

      - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

  - `HealthStatus ChatHealthStatus`

    **[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.

    - `DocURL string`

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

    - `Status string`

      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.

      - `const ChatHealthStatusStatusHealthy ChatHealthStatusStatus = "HEALTHY"`

      - `const ChatHealthStatusStatusAtRisk ChatHealthStatusStatus = "AT_RISK"`

      - `const ChatHealthStatusStatusCritical ChatHealthStatusStatus = "CRITICAL"`

      - `const ChatHealthStatusStatusOptedOut ChatHealthStatusStatus = "OPTED_OUT"`

    - `UpdatedAt Time`

      When this status last changed.

  - `IsArchived bool`

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

  - `IsGroup bool`

    Whether this is a group chat

  - `UpdatedAt Time`

    When the chat was last updated

  - `GroupChatIcon string`

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

  - `Service ServiceType`

    Messaging service type

### Link Part

- `type LinkPart struct{…}`

  - `Type LinkPartType`

    Indicates this is a rich link preview part

    - `const LinkPartTypeLink LinkPartType = "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

- `type MediaPart struct{…}`

  - `Type MediaPartType`

    Indicates this is a media attachment part

    - `const MediaPartTypeMedia MediaPartType = "media"`

  - `AttachmentID 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 bool`

    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

- `type MessageContent struct{…}`

  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 MessageEffectType`

      Type of effect

      - `const MessageEffectTypeScreen MessageEffectType = "screen"`

      - `const MessageEffectTypeBubble MessageEffectType = "bubble"`

  - `Experience MessageContentExperience`

    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 map[string, any]`

      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.

  - `IdempotencyKey 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 []MessageContentPartUnion`

    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`.

    - `type TextPart struct{…}`

      - `Type TextPartType`

        Indicates this is a text message part

        - `const TextPartTypeText TextPartType = "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.

      - `MentionRange []int64`

        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.

      - `TextDecorations []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 []int64`

          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 TextDecorationAnimation`

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

          - `const TextDecorationAnimationBig TextDecorationAnimation = "big"`

          - `const TextDecorationAnimationSmall TextDecorationAnimation = "small"`

          - `const TextDecorationAnimationShake TextDecorationAnimation = "shake"`

          - `const TextDecorationAnimationNod TextDecorationAnimation = "nod"`

          - `const TextDecorationAnimationExplode TextDecorationAnimation = "explode"`

          - `const TextDecorationAnimationRipple TextDecorationAnimation = "ripple"`

          - `const TextDecorationAnimationBloom TextDecorationAnimation = "bloom"`

          - `const TextDecorationAnimationJitter TextDecorationAnimation = "jitter"`

        - `Style TextDecorationStyle`

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

          - `const TextDecorationStyleBold TextDecorationStyle = "bold"`

          - `const TextDecorationStyleItalic TextDecorationStyle = "italic"`

          - `const TextDecorationStyleStrikethrough TextDecorationStyle = "strikethrough"`

          - `const TextDecorationStyleUnderline TextDecorationStyle = "underline"`

    - `type MediaPart struct{…}`

      - `Type MediaPartType`

        Indicates this is a media attachment part

        - `const MediaPartTypeMedia MediaPartType = "media"`

      - `AttachmentID 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 bool`

        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.

    - `type LinkPart struct{…}`

      - `Type LinkPartType`

        Indicates this is a rich link preview part

        - `const LinkPartTypeLink LinkPartType = "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.

    - `MessageContentPartIMessageApp`

      - `App MessageContentPartIMessageAppApp`

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

        - `BundleID string`

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

        - `Name string`

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

        - `TeamID string`

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

        - `AppStoreID int64`

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

      - `Layout MessageContentPartIMessageAppLayout`

        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.

        - `ImageSubtitle string`

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

        - `ImageTitle string`

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

        - `ImageURL 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.

        - `TrailingCaption string`

          Label shown top-right.

        - `TrailingSubcaption string`

          Label shown below `trailing_caption`, on the right.

      - `Type IMessageApp`

        Indicates this is an iMessage app card part.

        - `const IMessageAppIMessageApp IMessageApp = "imessage_app"`

      - `FallbackText string`

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

      - `Interactive bool`

        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).

    - `MessageContentPartAppClip`

      - `Type AppClip`

        Indicates this is an App Clip card

        - `const AppClipAppClip AppClip = "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.

  - `PreferredService ServiceType`

    Messaging service type

    - `const ServiceTypeIMessage ServiceType = "iMessage"`

    - `const ServiceTypeSMS ServiceType = "SMS"`

    - `const ServiceTypeRCS ServiceType = "RCS"`

  - `ReplyTo ReplyTo`

    Reply to another message to create a threaded conversation

    - `MessageID string`

      The ID of the message to reply to

    - `PartIndex int64`

      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

- `type TextPart struct{…}`

  - `Type TextPartType`

    Indicates this is a text message part

    - `const TextPartTypeText TextPartType = "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.

  - `MentionRange []int64`

    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.

  - `TextDecorations []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 []int64`

      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 TextDecorationAnimation`

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

      - `const TextDecorationAnimationBig TextDecorationAnimation = "big"`

      - `const TextDecorationAnimationSmall TextDecorationAnimation = "small"`

      - `const TextDecorationAnimationShake TextDecorationAnimation = "shake"`

      - `const TextDecorationAnimationNod TextDecorationAnimation = "nod"`

      - `const TextDecorationAnimationExplode TextDecorationAnimation = "explode"`

      - `const TextDecorationAnimationRipple TextDecorationAnimation = "ripple"`

      - `const TextDecorationAnimationBloom TextDecorationAnimation = "bloom"`

      - `const TextDecorationAnimationJitter TextDecorationAnimation = "jitter"`

    - `Style TextDecorationStyle`

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

      - `const TextDecorationStyleBold TextDecorationStyle = "bold"`

      - `const TextDecorationStyleItalic TextDecorationStyle = "italic"`

      - `const TextDecorationStyleStrikethrough TextDecorationStyle = "strikethrough"`

      - `const TextDecorationStyleUnderline TextDecorationStyle = "underline"`

# Participants

## Add a participant to a chat

`client.Chats.Participants.Add(ctx, chatID, body) (*ChatParticipantAddResponse, error)`

**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 ChatParticipantAddParams`

  - `Handle param.Field[string]`

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

### Returns

- `type ChatParticipantAddResponse struct{…}`

  - `Message string`

  - `Status string`

  - `TraceID string`

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  response, err := client.Chats.Participants.Add(
    context.TODO(),
    "550e8400-e29b-41d4-a716-446655440000",
    linqgo.ChatParticipantAddParams{
      Handle: "+12052499136",
    },
  )
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", response.TraceID)
}
```

#### Response

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

## Remove a participant from a chat

`client.Chats.Participants.Remove(ctx, chatID, body) (*ChatParticipantRemoveResponse, error)`

**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 ChatParticipantRemoveParams`

  - `Handle param.Field[string]`

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

### Returns

- `type ChatParticipantRemoveResponse struct{…}`

  - `Message string`

  - `Status string`

  - `TraceID string`

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  participant, err := client.Chats.Participants.Remove(
    context.TODO(),
    "550e8400-e29b-41d4-a716-446655440000",
    linqgo.ChatParticipantRemoveParams{
      Handle: "+12052499136",
    },
  )
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", participant.TraceID)
}
```

#### Response

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

# Typing

## Start typing indicator

`client.Chats.Typing.Start(ctx, chatID) error`

**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

```go
package main

import (
  "context"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  err := client.Chats.Typing.Start(context.TODO(), "550e8400-e29b-41d4-a716-446655440000")
  if err != nil {
    panic(err.Error())
  }
}
```

#### 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(ctx, chatID) error`

**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

```go
package main

import (
  "context"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  err := client.Chats.Typing.Stop(context.TODO(), "550e8400-e29b-41d4-a716-446655440000")
  if err != nil {
    panic(err.Error())
  }
}
```

#### 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(ctx, chatID, body) (*ChatMessageSendResponse, error)`

**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 ChatMessageSendParams`

  - `Message param.Field[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.

  - `OverrideOptout param.Field[bool]`

    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

- `type ChatMessageSendResponse struct{…}`

  Response for sending a message to a chat

  - `ChatID 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)

    - `CreatedAt Time`

      When the message was created

    - `DeliveryStatus SentMessageDeliveryStatus`

      Current delivery status of a message

      - `const SentMessageDeliveryStatusPending SentMessageDeliveryStatus = "pending"`

      - `const SentMessageDeliveryStatusQueued SentMessageDeliveryStatus = "queued"`

      - `const SentMessageDeliveryStatusSent SentMessageDeliveryStatus = "sent"`

      - `const SentMessageDeliveryStatusDelivered SentMessageDeliveryStatus = "delivered"`

      - `const SentMessageDeliveryStatusReceived SentMessageDeliveryStatus = "received"`

      - `const SentMessageDeliveryStatusRead SentMessageDeliveryStatus = "read"`

      - `const SentMessageDeliveryStatusFailed SentMessageDeliveryStatus = "failed"`

    - `IsRead bool`

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

    - `Parts []SentMessagePartUnion`

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

      - `type TextPartResponse struct{…}`

        A text message part

        - `Reactions []Reaction`

          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

            - `JoinedAt Time`

              When this participant joined the chat

            - `Service ServiceType`

              Messaging service type

              - `const ServiceTypeIMessage ServiceType = "iMessage"`

              - `const ServiceTypeSMS ServiceType = "SMS"`

              - `const ServiceTypeRCS ServiceType = "RCS"`

            - `IsMe bool`

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

            - `LeftAt Time`

              When they left (if applicable)

            - `Status ChatHandleStatus`

              Participant status

              - `const ChatHandleStatusActive ChatHandleStatus = "active"`

              - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

              - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

          - `IsMe bool`

            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.

            - `const ReactionTypeLove ReactionType = "love"`

            - `const ReactionTypeLike ReactionType = "like"`

            - `const ReactionTypeDislike ReactionType = "dislike"`

            - `const ReactionTypeLaugh ReactionType = "laugh"`

            - `const ReactionTypeEmphasize ReactionType = "emphasize"`

            - `const ReactionTypeQuestion ReactionType = "question"`

            - `const ReactionTypeCustom ReactionType = "custom"`

            - `const ReactionTypeSticker ReactionType = "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.

          - `CustomEmoji string`

            Custom emoji if type is "custom", null otherwise

          - `Sticker ReactionSticker`

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

            - `FileName string`

              Filename of the sticker

            - `Height int64`

              Sticker image height in pixels

            - `MimeType string`

              MIME type of the sticker image

            - `URL string`

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

            - `Width int64`

              Sticker image width in pixels

        - `Type TextPartResponseType`

          Indicates this is a text message part

          - `const TextPartResponseTypeText TextPartResponseType = "text"`

        - `Value string`

          The text content

        - `Mention string`

          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.

        - `MentionRange []int64`

          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 []TextPartResponseMention`

          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.

          - `IsMe bool`

            Whether the mentioned participant is this line.

          - `Range []int64`

            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.*

        - `TextDecorations []TextDecoration`

          Text decorations applied to character ranges in the value

          - `Range []int64`

            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 TextDecorationAnimation`

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

            - `const TextDecorationAnimationBig TextDecorationAnimation = "big"`

            - `const TextDecorationAnimationSmall TextDecorationAnimation = "small"`

            - `const TextDecorationAnimationShake TextDecorationAnimation = "shake"`

            - `const TextDecorationAnimationNod TextDecorationAnimation = "nod"`

            - `const TextDecorationAnimationExplode TextDecorationAnimation = "explode"`

            - `const TextDecorationAnimationRipple TextDecorationAnimation = "ripple"`

            - `const TextDecorationAnimationBloom TextDecorationAnimation = "bloom"`

            - `const TextDecorationAnimationJitter TextDecorationAnimation = "jitter"`

          - `Style TextDecorationStyle`

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

            - `const TextDecorationStyleBold TextDecorationStyle = "bold"`

            - `const TextDecorationStyleItalic TextDecorationStyle = "italic"`

            - `const TextDecorationStyleStrikethrough TextDecorationStyle = "strikethrough"`

            - `const TextDecorationStyleUnderline TextDecorationStyle = "underline"`

      - `type MediaPartResponse struct{…}`

        A media attachment part

        - `ID string`

          Unique attachment identifier

        - `Filename string`

          Original filename

        - `MimeType string`

          MIME type of the file

        - `Reactions []Reaction`

          Reactions on this message part

          - `Handle ChatHandle`

          - `IsMe bool`

            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.

          - `CustomEmoji string`

            Custom emoji if type is "custom", null otherwise

          - `Sticker ReactionSticker`

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

        - `SizeBytes int64`

          File size in bytes

        - `Type MediaPartResponseType`

          Indicates this is a media attachment part

          - `const MediaPartResponseTypeMedia MediaPartResponseType = "media"`

        - `URL string`

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

      - `type LinkPartResponse struct{…}`

        A rich link preview part

        - `Reactions []Reaction`

          Reactions on this message part

          - `Handle ChatHandle`

          - `IsMe bool`

            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.

          - `CustomEmoji string`

            Custom emoji if type is "custom", null otherwise

          - `Sticker ReactionSticker`

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

        - `Type LinkPartResponseType`

          Indicates this is a rich link preview part

          - `const LinkPartResponseTypeLink LinkPartResponseType = "link"`

        - `Value string`

          The URL

      - `type SentMessagePartIMessageAppPartResponse struct{…}`

        An iMessage app card part.

        - `App SentMessagePartIMessageAppPartResponseApp`

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

          - `BundleID string`

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

          - `Name string`

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

          - `TeamID string`

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

          - `AppStoreID int64`

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

        - `Layout SentMessagePartIMessageAppPartResponseLayout`

          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.

          - `ImageSubtitle string`

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

          - `ImageTitle string`

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

          - `ImageURL 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.

          - `TrailingCaption string`

            Label shown top-right.

          - `TrailingSubcaption string`

            Label shown below `trailing_caption`, on the right.

        - `Reactions []Reaction`

          Reactions on this message part

          - `Handle ChatHandle`

          - `IsMe bool`

            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.

          - `CustomEmoji string`

            Custom emoji if type is "custom", null otherwise

          - `Sticker ReactionSticker`

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

        - `Type string`

          Indicates this is an iMessage app card part.

          - `const SentMessagePartIMessageAppPartResponseTypeIMessageApp SentMessagePartIMessageAppPartResponseType = "imessage_app"`

        - `URL string`

          The URL delivered to the iMessage app on tap.

        - `FallbackText string`

          Fallback text for surfaces that cannot render the card.

      - `type SentMessagePartAppClipPartResponse struct{…}`

        An App Clip card part

        - `Reactions []Reaction`

          Reactions on this message part

          - `Handle ChatHandle`

          - `IsMe bool`

            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.

          - `CustomEmoji string`

            Custom emoji if type is "custom", null otherwise

          - `Sticker ReactionSticker`

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

        - `Type string`

          Indicates this is an App Clip card part

          - `const SentMessagePartAppClipPartResponseTypeAppClip SentMessagePartAppClipPartResponseType = "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

        - `ImageURL string`

          The card's preview image

        - `Title string`

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

    - `SentAt Time`

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

    - `DeliveredAt Time`

      When the message was delivered

    - `Effect MessageEffect`

      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 MessageEffectType`

        Type of effect

        - `const MessageEffectTypeScreen MessageEffectType = "screen"`

        - `const MessageEffectTypeBubble MessageEffectType = "bubble"`

    - `FromHandle ChatHandle`

      The sender of this message as a full handle object

    - `PreferredService ServiceType`

      Messaging service type

    - `ReplyTo ReplyTo`

      Indicates this message is a threaded reply to another message

      - `MessageID string`

        The ID of the message to reply to

      - `PartIndex int64`

        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`

      Messaging service type

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  response, err := client.Chats.Messages.Send(
    context.TODO(),
    "550e8400-e29b-41d4-a716-446655440000",
    linqgo.ChatMessageSendParams{
      Message: linqgo.MessageContentParam{
        Parts: []linqgo.MessageContentPartUnionParam{linqgo.MessageContentPartUnionParam{
          OfText: &linqgo.TextPartParam{
            Type: linqgo.TextPartTypeText,
            Value: "Hello, world!",
          },
        }},
      },
    },
  )
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", response.ChatID)
}
```

#### 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(ctx, chatID, query) (*ListMessagesPagination[Message], error)`

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

Retrieve messages from a specific chat with pagination support.

### Parameters

- `chatID string`

- `query ChatMessageListParams`

  - `Cursor param.Field[string]`

    Pagination cursor from previous next_cursor response

  - `Limit param.Field[int64]`

    Maximum number of messages to return

### Returns

- `type Message struct{…}`

  - `ID string`

    Unique identifier for the message

  - `ChatID string`

    ID of the chat this message belongs to

  - `CreatedAt Time`

    When the message was created

  - `DeliveryStatus MessageDeliveryStatus`

    Current delivery status of a message

    - `const MessageDeliveryStatusPending MessageDeliveryStatus = "pending"`

    - `const MessageDeliveryStatusQueued MessageDeliveryStatus = "queued"`

    - `const MessageDeliveryStatusSent MessageDeliveryStatus = "sent"`

    - `const MessageDeliveryStatusDelivered MessageDeliveryStatus = "delivered"`

    - `const MessageDeliveryStatusReceived MessageDeliveryStatus = "received"`

    - `const MessageDeliveryStatusRead MessageDeliveryStatus = "read"`

    - `const MessageDeliveryStatusFailed MessageDeliveryStatus = "failed"`

  - `IsDelivered bool`

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

  - `IsFromMe bool`

    Whether this message was sent by the authenticated user

  - `IsRead bool`

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

  - `UpdatedAt Time`

    When the message was last updated

  - `DeliveredAt Time`

    When the message was delivered

  - `Effect MessageEffect`

    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 MessageEffectType`

      Type of effect

      - `const MessageEffectTypeScreen MessageEffectType = "screen"`

      - `const MessageEffectTypeBubble MessageEffectType = "bubble"`

  - `From string`

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

  - `FromHandle ChatHandle`

    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

    - `JoinedAt Time`

      When this participant joined the chat

    - `Service ServiceType`

      Messaging service type

      - `const ServiceTypeIMessage ServiceType = "iMessage"`

      - `const ServiceTypeSMS ServiceType = "SMS"`

      - `const ServiceTypeRCS ServiceType = "RCS"`

    - `IsMe bool`

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

    - `LeftAt Time`

      When they left (if applicable)

    - `Status ChatHandleStatus`

      Participant status

      - `const ChatHandleStatusActive ChatHandleStatus = "active"`

      - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

      - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

  - `Parts []MessagePartUnion`

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

    - `type TextPartResponse struct{…}`

      A text message part

      - `Reactions []Reaction`

        Reactions on this message part

        - `Handle ChatHandle`

        - `IsMe bool`

          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.

          - `const ReactionTypeLove ReactionType = "love"`

          - `const ReactionTypeLike ReactionType = "like"`

          - `const ReactionTypeDislike ReactionType = "dislike"`

          - `const ReactionTypeLaugh ReactionType = "laugh"`

          - `const ReactionTypeEmphasize ReactionType = "emphasize"`

          - `const ReactionTypeQuestion ReactionType = "question"`

          - `const ReactionTypeCustom ReactionType = "custom"`

          - `const ReactionTypeSticker ReactionType = "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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

          - `FileName string`

            Filename of the sticker

          - `Height int64`

            Sticker image height in pixels

          - `MimeType string`

            MIME type of the sticker image

          - `URL string`

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

          - `Width int64`

            Sticker image width in pixels

      - `Type TextPartResponseType`

        Indicates this is a text message part

        - `const TextPartResponseTypeText TextPartResponseType = "text"`

      - `Value string`

        The text content

      - `Mention string`

        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.

      - `MentionRange []int64`

        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 []TextPartResponseMention`

        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.

        - `IsMe bool`

          Whether the mentioned participant is this line.

        - `Range []int64`

          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.*

      - `TextDecorations []TextDecoration`

        Text decorations applied to character ranges in the value

        - `Range []int64`

          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 TextDecorationAnimation`

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

          - `const TextDecorationAnimationBig TextDecorationAnimation = "big"`

          - `const TextDecorationAnimationSmall TextDecorationAnimation = "small"`

          - `const TextDecorationAnimationShake TextDecorationAnimation = "shake"`

          - `const TextDecorationAnimationNod TextDecorationAnimation = "nod"`

          - `const TextDecorationAnimationExplode TextDecorationAnimation = "explode"`

          - `const TextDecorationAnimationRipple TextDecorationAnimation = "ripple"`

          - `const TextDecorationAnimationBloom TextDecorationAnimation = "bloom"`

          - `const TextDecorationAnimationJitter TextDecorationAnimation = "jitter"`

        - `Style TextDecorationStyle`

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

          - `const TextDecorationStyleBold TextDecorationStyle = "bold"`

          - `const TextDecorationStyleItalic TextDecorationStyle = "italic"`

          - `const TextDecorationStyleStrikethrough TextDecorationStyle = "strikethrough"`

          - `const TextDecorationStyleUnderline TextDecorationStyle = "underline"`

    - `type MediaPartResponse struct{…}`

      A media attachment part

      - `ID string`

        Unique attachment identifier

      - `Filename string`

        Original filename

      - `MimeType string`

        MIME type of the file

      - `Reactions []Reaction`

        Reactions on this message part

        - `Handle ChatHandle`

        - `IsMe bool`

          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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

      - `SizeBytes int64`

        File size in bytes

      - `Type MediaPartResponseType`

        Indicates this is a media attachment part

        - `const MediaPartResponseTypeMedia MediaPartResponseType = "media"`

      - `URL string`

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

    - `type LinkPartResponse struct{…}`

      A rich link preview part

      - `Reactions []Reaction`

        Reactions on this message part

        - `Handle ChatHandle`

        - `IsMe bool`

          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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

      - `Type LinkPartResponseType`

        Indicates this is a rich link preview part

        - `const LinkPartResponseTypeLink LinkPartResponseType = "link"`

      - `Value string`

        The URL

    - `type MessagePartIMessageAppPartResponse struct{…}`

      An iMessage app card part.

      - `App MessagePartIMessageAppPartResponseApp`

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

        - `BundleID string`

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

        - `Name string`

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

        - `TeamID string`

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

        - `AppStoreID int64`

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

      - `Layout MessagePartIMessageAppPartResponseLayout`

        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.

        - `ImageSubtitle string`

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

        - `ImageTitle string`

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

        - `ImageURL 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.

        - `TrailingCaption string`

          Label shown top-right.

        - `TrailingSubcaption string`

          Label shown below `trailing_caption`, on the right.

      - `Reactions []Reaction`

        Reactions on this message part

        - `Handle ChatHandle`

        - `IsMe bool`

          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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

      - `Type string`

        Indicates this is an iMessage app card part.

        - `const MessagePartIMessageAppPartResponseTypeIMessageApp MessagePartIMessageAppPartResponseType = "imessage_app"`

      - `URL string`

        The URL delivered to the iMessage app on tap.

      - `FallbackText string`

        Fallback text for surfaces that cannot render the card.

    - `type MessagePartAppClipPartResponse struct{…}`

      An App Clip card part

      - `Reactions []Reaction`

        Reactions on this message part

        - `Handle ChatHandle`

        - `IsMe bool`

          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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

      - `Type string`

        Indicates this is an App Clip card part

        - `const MessagePartAppClipPartResponseTypeAppClip MessagePartAppClipPartResponseType = "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

      - `ImageURL string`

        The card's preview image

      - `Title string`

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

  - `PreferredService ServiceType`

    Messaging service type

  - `ReadAt Time`

    When the message was read

  - `ReconciledAt Time`

    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.

  - `ReplyTo ReplyTo`

    Indicates this message is a threaded reply to another message

    - `MessageID string`

      The ID of the message to reply to

    - `PartIndex int64`

      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.

  - `SentAt Time`

    When the message was sent

  - `Service ServiceType`

    Messaging service type

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  page, err := client.Chats.Messages.List(
    context.TODO(),
    "550e8400-e29b-41d4-a716-446655440000",
    linqgo.ChatMessageListParams{

    },
  )
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", page)
}
```

#### 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

- `type SentMessage struct{…}`

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

  - `ID string`

    Message identifier (UUID)

  - `CreatedAt Time`

    When the message was created

  - `DeliveryStatus SentMessageDeliveryStatus`

    Current delivery status of a message

    - `const SentMessageDeliveryStatusPending SentMessageDeliveryStatus = "pending"`

    - `const SentMessageDeliveryStatusQueued SentMessageDeliveryStatus = "queued"`

    - `const SentMessageDeliveryStatusSent SentMessageDeliveryStatus = "sent"`

    - `const SentMessageDeliveryStatusDelivered SentMessageDeliveryStatus = "delivered"`

    - `const SentMessageDeliveryStatusReceived SentMessageDeliveryStatus = "received"`

    - `const SentMessageDeliveryStatusRead SentMessageDeliveryStatus = "read"`

    - `const SentMessageDeliveryStatusFailed SentMessageDeliveryStatus = "failed"`

  - `IsRead bool`

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

  - `Parts []SentMessagePartUnion`

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

    - `type TextPartResponse struct{…}`

      A text message part

      - `Reactions []Reaction`

        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

          - `JoinedAt Time`

            When this participant joined the chat

          - `Service ServiceType`

            Messaging service type

            - `const ServiceTypeIMessage ServiceType = "iMessage"`

            - `const ServiceTypeSMS ServiceType = "SMS"`

            - `const ServiceTypeRCS ServiceType = "RCS"`

          - `IsMe bool`

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

          - `LeftAt Time`

            When they left (if applicable)

          - `Status ChatHandleStatus`

            Participant status

            - `const ChatHandleStatusActive ChatHandleStatus = "active"`

            - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

            - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

        - `IsMe bool`

          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.

          - `const ReactionTypeLove ReactionType = "love"`

          - `const ReactionTypeLike ReactionType = "like"`

          - `const ReactionTypeDislike ReactionType = "dislike"`

          - `const ReactionTypeLaugh ReactionType = "laugh"`

          - `const ReactionTypeEmphasize ReactionType = "emphasize"`

          - `const ReactionTypeQuestion ReactionType = "question"`

          - `const ReactionTypeCustom ReactionType = "custom"`

          - `const ReactionTypeSticker ReactionType = "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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

          - `FileName string`

            Filename of the sticker

          - `Height int64`

            Sticker image height in pixels

          - `MimeType string`

            MIME type of the sticker image

          - `URL string`

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

          - `Width int64`

            Sticker image width in pixels

      - `Type TextPartResponseType`

        Indicates this is a text message part

        - `const TextPartResponseTypeText TextPartResponseType = "text"`

      - `Value string`

        The text content

      - `Mention string`

        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.

      - `MentionRange []int64`

        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 []TextPartResponseMention`

        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.

        - `IsMe bool`

          Whether the mentioned participant is this line.

        - `Range []int64`

          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.*

      - `TextDecorations []TextDecoration`

        Text decorations applied to character ranges in the value

        - `Range []int64`

          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 TextDecorationAnimation`

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

          - `const TextDecorationAnimationBig TextDecorationAnimation = "big"`

          - `const TextDecorationAnimationSmall TextDecorationAnimation = "small"`

          - `const TextDecorationAnimationShake TextDecorationAnimation = "shake"`

          - `const TextDecorationAnimationNod TextDecorationAnimation = "nod"`

          - `const TextDecorationAnimationExplode TextDecorationAnimation = "explode"`

          - `const TextDecorationAnimationRipple TextDecorationAnimation = "ripple"`

          - `const TextDecorationAnimationBloom TextDecorationAnimation = "bloom"`

          - `const TextDecorationAnimationJitter TextDecorationAnimation = "jitter"`

        - `Style TextDecorationStyle`

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

          - `const TextDecorationStyleBold TextDecorationStyle = "bold"`

          - `const TextDecorationStyleItalic TextDecorationStyle = "italic"`

          - `const TextDecorationStyleStrikethrough TextDecorationStyle = "strikethrough"`

          - `const TextDecorationStyleUnderline TextDecorationStyle = "underline"`

    - `type MediaPartResponse struct{…}`

      A media attachment part

      - `ID string`

        Unique attachment identifier

      - `Filename string`

        Original filename

      - `MimeType string`

        MIME type of the file

      - `Reactions []Reaction`

        Reactions on this message part

        - `Handle ChatHandle`

        - `IsMe bool`

          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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

      - `SizeBytes int64`

        File size in bytes

      - `Type MediaPartResponseType`

        Indicates this is a media attachment part

        - `const MediaPartResponseTypeMedia MediaPartResponseType = "media"`

      - `URL string`

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

    - `type LinkPartResponse struct{…}`

      A rich link preview part

      - `Reactions []Reaction`

        Reactions on this message part

        - `Handle ChatHandle`

        - `IsMe bool`

          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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

      - `Type LinkPartResponseType`

        Indicates this is a rich link preview part

        - `const LinkPartResponseTypeLink LinkPartResponseType = "link"`

      - `Value string`

        The URL

    - `type SentMessagePartIMessageAppPartResponse struct{…}`

      An iMessage app card part.

      - `App SentMessagePartIMessageAppPartResponseApp`

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

        - `BundleID string`

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

        - `Name string`

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

        - `TeamID string`

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

        - `AppStoreID int64`

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

      - `Layout SentMessagePartIMessageAppPartResponseLayout`

        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.

        - `ImageSubtitle string`

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

        - `ImageTitle string`

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

        - `ImageURL 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.

        - `TrailingCaption string`

          Label shown top-right.

        - `TrailingSubcaption string`

          Label shown below `trailing_caption`, on the right.

      - `Reactions []Reaction`

        Reactions on this message part

        - `Handle ChatHandle`

        - `IsMe bool`

          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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

      - `Type string`

        Indicates this is an iMessage app card part.

        - `const SentMessagePartIMessageAppPartResponseTypeIMessageApp SentMessagePartIMessageAppPartResponseType = "imessage_app"`

      - `URL string`

        The URL delivered to the iMessage app on tap.

      - `FallbackText string`

        Fallback text for surfaces that cannot render the card.

    - `type SentMessagePartAppClipPartResponse struct{…}`

      An App Clip card part

      - `Reactions []Reaction`

        Reactions on this message part

        - `Handle ChatHandle`

        - `IsMe bool`

          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.

        - `CustomEmoji string`

          Custom emoji if type is "custom", null otherwise

        - `Sticker ReactionSticker`

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

      - `Type string`

        Indicates this is an App Clip card part

        - `const SentMessagePartAppClipPartResponseTypeAppClip SentMessagePartAppClipPartResponseType = "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

      - `ImageURL string`

        The card's preview image

      - `Title string`

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

  - `SentAt Time`

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

  - `DeliveredAt Time`

    When the message was delivered

  - `Effect MessageEffect`

    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 MessageEffectType`

      Type of effect

      - `const MessageEffectTypeScreen MessageEffectType = "screen"`

      - `const MessageEffectTypeBubble MessageEffectType = "bubble"`

  - `FromHandle ChatHandle`

    The sender of this message as a full handle object

  - `PreferredService ServiceType`

    Messaging service type

  - `ReplyTo ReplyTo`

    Indicates this message is a threaded reply to another message

    - `MessageID string`

      The ID of the message to reply to

    - `PartIndex int64`

      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`

    Messaging service type

# Location

## Request location sharing

`client.Chats.Location.Request(ctx, chatID) (*LocationRequestResponse, error)`

**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

- `type LocationRequestResponse struct{…}`

  - `Message string`

  - `Success bool`

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  locationRequestResponse, err := client.Chats.Location.Request(context.TODO(), "975d0776-bd17-4273-8337-f346b4c661b0")
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", locationRequestResponse.Message)
}
```

#### Response

```json
{
  "success": true,
  "message": "Location request sent"
}
```

## Get location data

`client.Chats.Location.Get(ctx, chatID) (*GetChatLocationResponse, error)`

**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

- `type GetChatLocationResponse struct{…}`

  - `Data GetChatLocationResponseData`

    - `Features []GetChatLocationResponseDataFeature`

      - `Geometry GetChatLocationResponseDataFeatureGeometry`

        - `Coordinates []float64`

          [longitude, latitude]

        - `Type string`

          - `const GetChatLocationResponseDataFeatureGeometryTypePoint GetChatLocationResponseDataFeatureGeometryType = "Point"`

      - `Properties GetChatLocationResponseDataFeatureProperties`

        - `Handle string`

          Phone number or email of the person sharing their location

        - `Address string`

          Full street address

        - `Locality string`

          City or locality name

        - `UpdatedAt Time`

          When the location was last updated

      - `Type string`

        - `const GetChatLocationResponseDataFeatureTypeFeature GetChatLocationResponseDataFeatureType = "Feature"`

    - `Type string`

      - `const GetChatLocationResponseDataTypeFeatureCollection GetChatLocationResponseDataType = "FeatureCollection"`

  - `Success bool`

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  getChatLocationResponse, err := client.Chats.Location.Get(context.TODO(), "975d0776-bd17-4273-8337-f346b4c661b0")
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", 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(ctx, chatID, body) (*StopChatLocationSharingResponse, error)`

**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 ChatLocationStopParams`

  - `Handle param.Field[string]`

    Phone number (E.164 format) or email address of the contact whose share to end

### Returns

- `type StopChatLocationSharingResponse struct{…}`

  - `Message string`

  - `Success bool`

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  stopChatLocationSharingResponse, err := client.Chats.Location.Stop(
    context.TODO(),
    "975d0776-bd17-4273-8337-f346b4c661b0",
    linqgo.ChatLocationStopParams{
      Handle: "+15551234567",
    },
  )
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", stopChatLocationSharingResponse.Message)
}
```

#### Response

```json
{
  "success": true,
  "message": "Location sharing stop requested"
}
```

## Domain Types

### Get Chat Location Response

- `type GetChatLocationResponse struct{…}`

  - `Data GetChatLocationResponseData`

    - `Features []GetChatLocationResponseDataFeature`

      - `Geometry GetChatLocationResponseDataFeatureGeometry`

        - `Coordinates []float64`

          [longitude, latitude]

        - `Type string`

          - `const GetChatLocationResponseDataFeatureGeometryTypePoint GetChatLocationResponseDataFeatureGeometryType = "Point"`

      - `Properties GetChatLocationResponseDataFeatureProperties`

        - `Handle string`

          Phone number or email of the person sharing their location

        - `Address string`

          Full street address

        - `Locality string`

          City or locality name

        - `UpdatedAt Time`

          When the location was last updated

      - `Type string`

        - `const GetChatLocationResponseDataFeatureTypeFeature GetChatLocationResponseDataFeatureType = "Feature"`

    - `Type string`

      - `const GetChatLocationResponseDataTypeFeatureCollection GetChatLocationResponseDataType = "FeatureCollection"`

  - `Success bool`

### Location Request Response

- `type LocationRequestResponse struct{…}`

  - `Message string`

  - `Success bool`

### Stop Chat Location Sharing Response

- `type StopChatLocationSharingResponse struct{…}`

  - `Message string`

  - `Success bool`

# Polls

## Create and send a poll in a chat

`client.Chats.Polls.New(ctx, chatID, body) (*PollEnvelope, error)`

**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 ChatPollNewParams`

  - `Poll param.Field[ChatPollNewParamsPoll]`

    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 []ChatPollNewParamsPollOption`

      - `Text string`

    - `IdempotencyKey string`

      Optional key to deduplicate the poll creation.

### Returns

- `type PollEnvelope struct{…}`

  Message-level envelope returned by every poll endpoint.

  - `ChatID string`

  - `CreatedAt Time`

  - `MessageID string`

    The poll-definition message's ID — reference this poll by it.

  - `Poll Poll`

    Poll content — options and the aggregate voter count.

    - `Options []PollOption`

      - `CanBeEdited bool`

      - `CreatorHandle 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

        - `JoinedAt Time`

          When this participant joined the chat

        - `Service ServiceType`

          Messaging service type

          - `const ServiceTypeIMessage ServiceType = "iMessage"`

          - `const ServiceTypeSMS ServiceType = "SMS"`

          - `const ServiceTypeRCS ServiceType = "RCS"`

        - `IsMe bool`

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

        - `LeftAt Time`

          When they left (if applicable)

        - `Status ChatHandleStatus`

          Participant status

          - `const ChatHandleStatusActive ChatHandleStatus = "active"`

          - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

          - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

      - `OptionID string`

      - `Text string`

      - `Voters []PollOptionVoter`

        Participants who voted for this option (vote_count = voters.length).

        - `Handle string`

        - `VotedAt Time`

    - `TotalVoters int64`

      Distinct participants across the whole poll (a voter picking two options counts once).

  - `Reactions []Reaction`

    Tapbacks/stickers on the whole poll (message part 0).

    - `Handle ChatHandle`

    - `IsMe bool`

      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.

      - `const ReactionTypeLove ReactionType = "love"`

      - `const ReactionTypeLike ReactionType = "like"`

      - `const ReactionTypeDislike ReactionType = "dislike"`

      - `const ReactionTypeLaugh ReactionType = "laugh"`

      - `const ReactionTypeEmphasize ReactionType = "emphasize"`

      - `const ReactionTypeQuestion ReactionType = "question"`

      - `const ReactionTypeCustom ReactionType = "custom"`

      - `const ReactionTypeSticker ReactionType = "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.

    - `CustomEmoji string`

      Custom emoji if type is "custom", null otherwise

    - `Sticker ReactionSticker`

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

      - `FileName string`

        Filename of the sticker

      - `Height int64`

        Sticker image height in pixels

      - `MimeType string`

        MIME type of the sticker image

      - `URL string`

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

      - `Width int64`

        Sticker image width in pixels

  - `UpdatedAt Time`

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  pollEnvelope, err := client.Chats.Polls.New(
    context.TODO(),
    "550e8400-e29b-41d4-a716-446655440000",
    linqgo.ChatPollNewParams{
      Poll: linqgo.ChatPollNewParamsPoll{
        Options: []linqgo.ChatPollNewParamsPollOption{linqgo.ChatPollNewParamsPollOption{
          Text: "Tacos",
        }, linqgo.ChatPollNewParamsPollOption{
          Text: "Sushi",
        }},
        IdempotencyKey: linqgo.String("poll-abc123"),
      },
    },
  )
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", pollEnvelope.ChatID)
}
```

#### 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

- `type Poll struct{…}`

  Poll content — options and the aggregate voter count.

  - `Options []PollOption`

    - `CanBeEdited bool`

    - `CreatorHandle 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

      - `JoinedAt Time`

        When this participant joined the chat

      - `Service ServiceType`

        Messaging service type

        - `const ServiceTypeIMessage ServiceType = "iMessage"`

        - `const ServiceTypeSMS ServiceType = "SMS"`

        - `const ServiceTypeRCS ServiceType = "RCS"`

      - `IsMe bool`

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

      - `LeftAt Time`

        When they left (if applicable)

      - `Status ChatHandleStatus`

        Participant status

        - `const ChatHandleStatusActive ChatHandleStatus = "active"`

        - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

        - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

    - `OptionID string`

    - `Text string`

    - `Voters []PollOptionVoter`

      Participants who voted for this option (vote_count = voters.length).

      - `Handle string`

      - `VotedAt Time`

  - `TotalVoters int64`

    Distinct participants across the whole poll (a voter picking two options counts once).

### Poll Envelope

- `type PollEnvelope struct{…}`

  Message-level envelope returned by every poll endpoint.

  - `ChatID string`

  - `CreatedAt Time`

  - `MessageID string`

    The poll-definition message's ID — reference this poll by it.

  - `Poll Poll`

    Poll content — options and the aggregate voter count.

    - `Options []PollOption`

      - `CanBeEdited bool`

      - `CreatorHandle 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

        - `JoinedAt Time`

          When this participant joined the chat

        - `Service ServiceType`

          Messaging service type

          - `const ServiceTypeIMessage ServiceType = "iMessage"`

          - `const ServiceTypeSMS ServiceType = "SMS"`

          - `const ServiceTypeRCS ServiceType = "RCS"`

        - `IsMe bool`

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

        - `LeftAt Time`

          When they left (if applicable)

        - `Status ChatHandleStatus`

          Participant status

          - `const ChatHandleStatusActive ChatHandleStatus = "active"`

          - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

          - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

      - `OptionID string`

      - `Text string`

      - `Voters []PollOptionVoter`

        Participants who voted for this option (vote_count = voters.length).

        - `Handle string`

        - `VotedAt Time`

    - `TotalVoters int64`

      Distinct participants across the whole poll (a voter picking two options counts once).

  - `Reactions []Reaction`

    Tapbacks/stickers on the whole poll (message part 0).

    - `Handle ChatHandle`

    - `IsMe bool`

      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.

      - `const ReactionTypeLove ReactionType = "love"`

      - `const ReactionTypeLike ReactionType = "like"`

      - `const ReactionTypeDislike ReactionType = "dislike"`

      - `const ReactionTypeLaugh ReactionType = "laugh"`

      - `const ReactionTypeEmphasize ReactionType = "emphasize"`

      - `const ReactionTypeQuestion ReactionType = "question"`

      - `const ReactionTypeCustom ReactionType = "custom"`

      - `const ReactionTypeSticker ReactionType = "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.

    - `CustomEmoji string`

      Custom emoji if type is "custom", null otherwise

    - `Sticker ReactionSticker`

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

      - `FileName string`

        Filename of the sticker

      - `Height int64`

        Sticker image height in pixels

      - `MimeType string`

        MIME type of the sticker image

      - `URL string`

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

      - `Width int64`

        Sticker image width in pixels

  - `UpdatedAt Time`

# Background

## Set chat background

`client.Chats.Background.Set(ctx, chatID, body) error`

**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 ChatBackgroundSetParams`

  - `Type param.Field[ChatBackgroundSetParamsType]`

    The background family.

    - `const ChatBackgroundSetParamsTypeColor ChatBackgroundSetParamsType = "color"`

    - `const ChatBackgroundSetParamsTypeDynamic ChatBackgroundSetParamsType = "dynamic"`

    - `const ChatBackgroundSetParamsTypePhoto ChatBackgroundSetParamsType = "photo"`

  - `ImageURL param.Field[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 param.Field[[]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 param.Field[ChatBackgroundSetParamsStyle]`

    Dynamic: the animated style — `sky`, `water`, or `aurora`.

    - `const ChatBackgroundSetParamsStyleSky ChatBackgroundSetParamsStyle = "sky"`

    - `const ChatBackgroundSetParamsStyleWater ChatBackgroundSetParamsStyle = "water"`

    - `const ChatBackgroundSetParamsStyleAurora ChatBackgroundSetParamsStyle = "aurora"`

  - `Variant param.Field[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

```go
package main

import (
  "context"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  err := client.Chats.Background.Set(
    context.TODO(),
    "550e8400-e29b-41d4-a716-446655440000",
    linqgo.ChatBackgroundSetParams{
      Type: linqgo.ChatBackgroundSetParamsTypeColor,
      Variant: linqgo.String("mango"),
    },
  )
  if err != nil {
    panic(err.Error())
  }
}
```

#### 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(ctx, chatID) error`

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

Remove the transcript background from a chat, resetting it to the default.

### Parameters

- `chatID string`

### Example

```go
package main

import (
  "context"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  err := client.Chats.Background.Remove(context.TODO(), "550e8400-e29b-41d4-a716-446655440000")
  if err != nil {
    panic(err.Error())
  }
}
```

#### 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
}
```
