openapi: 3.1.0
info:
  title: Linq Partner API
  version: 1.0.0
  description: |
    The Linq Partner API enables you to send and receive iMessages programmatically at scale. Build powerful messaging experiences, automate conversations, and integrate iMessage infrastructure directly into your applications.

    Messages are sent through Apple's iMessage protocol using real devices, ensuring full compatibility with all iMessage features including read receipts, typing indicators, reactions, and rich media attachments.

    ## Getting Started

    All API requests require authentication via the `Authorization: Bearer` header. Your bearer token determines which phone numbers you can send from and which data you can access.

    ```bash
    curl https://api.linqapp.com/api/partner/v3/chats \
      -H "Authorization: Bearer your_token_here"
    ```

    ## Key Concepts

    - **Chats**: Conversation threads with one or more participants
    - **Messages**: Individual messages within a chat, supporting text, attachments, effects, and reactions
    - **Participants**: Phone numbers or email addresses involved in a conversation
    - **Attachments**: Files (images, videos, documents, audio) sent with messages
    - **Webhooks**: Real-time notifications for incoming messages, reactions, and events

    ## Trace Context (OpenTelemetry)

    The Linq API supports [W3C Trace Context](https://www.w3.org/TR/trace-context/) for distributed tracing. Every API response includes an `X-Trace-ID` response header that can be used for debugging and support requests.
    Error responses and webhook payloads also include `trace_id` in the response body for convenience.

    Linq generates a new trace context for each incoming request. Any `traceparent` or `tracestate` headers you provide will be replaced with a server-generated trace ID, per the W3C specification's security recommendations for public APIs acting as trust boundaries.

    ## Example Apps

    These open-source example apps show what you can build with the Linq Partner API. Each repo is ready to clone, configure with your API keys, and run.

    ### AI Agent
    A complete, open-source example of a Claude-powered AI agent running on iMessage via the Linq Blue API. Fork it, customize it, and deploy your own in minutes.

    [🔗 Use Case](https://linqapp.com/s/use-cases/ai-agent)

    [📦 GitHub Repo](https://github.com/linq-team/ai-agent-example)

    ### Bookings Agent
    A booking agent powered by Claude that searches restaurants, books tables, and manages reservations through Resy — all from iMessage, built on Linq. Text the number to try it, or clone the open-source repo to run your own.

    [🔗 Use Case](https://linqapp.com/s/use-cases/bookings-agent)

    [📦 GitHub Repo](https://github.com/linq-team/linq-resy-agent)

    ### Markets Agent
    Kai is an open-source trading agent with 14 Claude tools, RSA-PSS signed Kalshi API calls, and rich iMessage features — built on Linq. Search markets, place trades, and track your portfolio entirely via text.

    [🔗 Use Case](https://linqapp.com/s/use-cases/kalshi-agent)

    [📦 GitHub Repo](https://github.com/linq-team/kalshi-agent)
  contact:
    name: Linq API Support
    email: support@linq.com
servers:
  - url: https://api.linqapp.com/api/partner
    description: Production server
security:
  - BearerAuth: []
tags:
  - name: Chats
    x-page-title: Chats
    x-page-icon: comments
    description: |
      A Chat is a conversation thread with one or more participants.

      To begin a chat, you must create a Chat with at least one recipient handle.
      Including multiple handles creates a group chat.

      When creating a chat, the `from` field specifies which of your
      authorized phone numbers the message originates from. Your authentication token grants
      access to one or more phone numbers, but the `from` field determines the actual sender.

      **Handle Format:**
      - Handles can be phone numbers or email addresses
      - Phone numbers MUST be in E.164 format (starting with +)
      - Phone format: `+[country code][subscriber number]`
      - Example phone: `+12223334444` (US), `+442071234567` (UK), `+81312345678` (Japan)
      - Example email: `user@example.com`
      - No spaces, dashes, or parentheses in phone numbers
  - name: Messages
    x-page-title: Messages
    x-page-icon: paper-plane
    description: |
      Messages are individual communications within a chat thread.

      Messages can include text, media attachments, rich link previews, special effects
      (like confetti or fireworks), and reactions. All messages are associated with a
      specific chat and sent from a phone number you own.

      Messages support delivery status tracking, read receipts, and editing capabilities.

      ## Rich Link Previews

      Send a URL as a `link` part to deliver it with a rich preview card showing the
      page's title, description, and image (when available). A `link` part must be the
      **only** part in the message — it cannot be combined with text or media parts.
      To send a URL without a preview card, include it in a `text` part instead.

      **Limitations:**
      - A `link` part cannot be combined with other parts in the same message.
      - Maximum URL length: 2,048 characters.
  - name: Attachments
    x-page-title: Attachments
    x-page-icon: paperclip
    description: |
      Send files (images, videos, documents, audio) with messages by providing a URL in a media part.
      Pre-uploading via `POST /v3/attachments` is **optional** and only needed for specific optimization scenarios.

      ## Sending Media via URL (up to 10MB)

      Provide a publicly accessible HTTPS URL with a [supported media type](#supported-file-types) in the `url` field of a media part.

      ```json
      {
        "parts": [
          { "type": "media", "url": "https://your-cdn.com/images/photo.jpg" }
        ]
      }
      ```

      This works with any URL you already host — no pre-upload step required. **Maximum file size: 10MB.**

      ## Pre-Upload (required for files over 10MB)

      Use `POST /v3/attachments` when you want to:
      - **Send files larger than 10MB** (up to 100MB) — URL-based downloads are limited to 10MB
      - **Send the same file to many recipients** — upload once, reuse the `attachment_id` without re-downloading each time
      - **Reduce message send latency** — the file is already stored, so sending is faster

      **How it works:**
      1. `POST /v3/attachments` with file metadata → returns a presigned `upload_url` (valid for **15 minutes**) and a permanent `attachment_id`
      2. PUT the raw file bytes to the `upload_url` with the `required_headers` (no JSON or multipart — just the binary content)
      3. Reference the `attachment_id` in your media part when sending messages (no expiration)

      **Key difference:** When you provide an external `url`, we download and process the file on every send.
      When you use a pre-uploaded `attachment_id`, the file is already stored — so repeated sends skip the download step entirely.

      ## Domain Allowlisting

      Attachment URLs in API responses are served from `cdn.linqapp.com`. This includes:
      - `url` fields in media and voice memo message parts
      - `download_url` fields in attachment and upload response objects

      If your application enforces domain allowlists (e.g., for SSRF protection), add:

      ```
      cdn.linqapp.com
      ```

      ## Supported File Types

      - **Images:** JPEG, PNG, GIF, HEIC, HEIF, TIFF, BMP
      - **Videos:** MP4, MOV, M4V
      - **Audio:** M4A, AAC, MP3, WAV, AIFF, CAF, AMR
      - **Documents:** PDF, TXT, RTF, CSV, Office formats, ZIP
      - **Contact & Calendar:** VCF, ICS

      ## Audio: Attachment vs Voice Memo

      Audio files sent as media parts appear as **downloadable file attachments** in iMessage.
      To send audio as an **iMessage voice memo bubble** (with native inline playback UI),
      use the dedicated `POST /v3/chats/{chatId}/voicememo` endpoint instead.

      ## File Size Limits

      - **URL-based (`url` field):** 10MB maximum
      - **Pre-upload (`attachment_id`):** 100MB maximum
  - name: Phone Numbers
    x-page-title: Phone Numbers
    x-page-icon: phone
    description: |
      Phone Numbers represent the phone numbers assigned to your partner account.

      Use the list phone numbers endpoint to discover which phone numbers are available
      for sending messages.

      When creating chats, listing chats, or sending a voice memo, use one of your assigned phone numbers
      in the `from` field.
  - name: Contact Card
    x-page-title: Contact Card
    x-page-icon: address-card
    description: |
      Contact Card lets you set and share your contact information (name and profile photo) with chat participants via iMessage Name and Photo Sharing.

      Use `POST /v3/contact_card` to create or update a card for a phone number.
      Use `PATCH /v3/contact_card` to update an existing active card.
      Use `GET /v3/contact_card` to retrieve the active card(s) for your partner account.
  - name: Capability Checks
    x-page-title: Capability Checks
    x-page-icon: signal-bars
    description: |
      Check whether a recipient address supports iMessage or RCS before sending a message.
  - name: Webhooks
    x-page-title: Webhooks
    x-page-icon: bell
    description: |
      Webhook Subscriptions allow you to receive real-time notifications when events
      occur on your account.

      Configure webhook endpoints to receive events such as messages sent/received,
      delivery status changes, reactions, typing indicators, and more.

      Failed deliveries (5xx, 429, network errors) are retried up to 10 times over
      ~25 minutes with exponential backoff. Each event includes a unique ID for
      deduplication.

      ## Webhook Headers

      Each webhook request includes the following headers:

      | Header | Description |
      |--------|-------------|
      | `X-Webhook-Event` | The event type (e.g., `message.sent`, `message.received`) |
      | `X-Webhook-Subscription-ID` | Your webhook subscription ID |
      | `X-Webhook-Timestamp` | Unix timestamp (seconds) when the webhook was sent |
      | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |

      ## Verifying Webhook Signatures

      All webhooks are signed using HMAC-SHA256. You should always verify the signature
      to ensure the webhook originated from Linq and hasn't been tampered with.

      **Signature Construction:**

      The signature is computed over a concatenation of the timestamp and payload:

      ```
      {timestamp}.{payload}
      ```

      Where:
      - `timestamp` is the value from the `X-Webhook-Timestamp` header
      - `payload` is the raw JSON request body (exact bytes, not re-serialized)

      **Verification Steps:**

      1. Extract the `X-Webhook-Timestamp` and `X-Webhook-Signature` headers
      2. Get the raw request body bytes (do not parse and re-serialize)
      3. Concatenate: `"{timestamp}.{payload}"`
      4. Compute HMAC-SHA256 using your signing secret as the key
      5. Hex-encode the result and compare with `X-Webhook-Signature`
      6. Use constant-time comparison to prevent timing attacks

      **Example (Python):**

      ```python
      import hmac
      import hashlib

      def verify_webhook(signing_secret, payload, timestamp, signature):
          message = f"{timestamp}.{payload.decode('utf-8')}"
          expected = hmac.new(
              signing_secret.encode('utf-8'),
              message.encode('utf-8'),
              hashlib.sha256
          ).hexdigest()
          return hmac.compare_digest(expected, signature)
      ```

      **Example (Node.js):**

      ```javascript
      const crypto = require('crypto');

      function verifyWebhook(signingSecret, payload, timestamp, signature) {
        const message = `${timestamp}.${payload}`;
        const expected = crypto
          .createHmac('sha256', signingSecret)
          .update(message)
          .digest('hex');
        return crypto.timingSafeEqual(
          Buffer.from(expected),
          Buffer.from(signature)
        );
      }
      ```

      **Security Best Practices:**

      - Reject webhooks with timestamps older than 5 minutes to prevent replay attacks
      - Always use constant-time comparison for signature verification
      - Store your signing secret securely (e.g., environment variable, secrets manager)
      - Return a 2xx status code quickly, then process the webhook asynchronously
  - name: Webhook Events
    x-page-title: Webhook Events
    x-page-icon: bolt
    description: |
      Webhook event definitions for the Linq API.

      These webhooks are delivered to your configured endpoint when events occur
      in your messaging infrastructure.

      ## Webhook Versioning

      Webhook payloads are versioned using dates. **We strongly recommend specifying a version**
      when creating webhook subscriptions by adding `?version=YYYY-MM-DD` to your target URL.

      ### Available Versions

      | Subscription Created | `webhook_version` |
      |---------------------|-------------------|
      | Before 2026-02-03 | `2025-01-01` |
      | 2026-02-03 or later | `2026-02-03` |

      ### How to Specify a Version

      Add `?version=YYYY-MM-DD` to your webhook subscription URL:

      ```
      https://your-server.com/webhook?version=2026-02-03
      ```

      If no version is specified, the subscription uses the latest available version at creation time.

      > **Warning:** If you delete and recreate a subscription without specifying a version,
      > you may receive a different payload format than before. Always check the `webhook_version`
      > in your current payloads before recreating subscriptions to ensure continuity.

      ### Differences Between Versions

      Only **message events** have different payload structures between versions:

      | Field | `2025-01-01` | `2026-02-03` |
      |-------|--------------|--------------|
      | Direction | `is_from_me: true/false` | `direction: "outbound"/"inbound"` |
      | Sender | `from_handle` | `sender_handle` |
      | Chat info | `chat_id`, `is_group` at top level | `chat` object with `id`, `is_group`, `owner_handle` |
      | Message fields | Nested in `message` object | Flat at top level (`id`, `parts`, `sent_at`, etc.) |

      All other events (reactions, participants, chat events, typing indicators) use the same format regardless of version.

      ## Webhook Headers

      All webhook requests include these headers:

      | Header | Description |
      |--------|-------------|
      | `X-Webhook-Signature` | HMAC-SHA256 signature (hex-encoded) |
      | `X-Webhook-Timestamp` | Unix timestamp (seconds) when the webhook was sent |
      | `X-Webhook-Event` | Event type (e.g., `message.received`) |
      | `X-Webhook-Subscription-ID` | Your webhook subscription ID |

      ## Signature Verification

      All webhooks are signed with HMAC-SHA256 using your webhook signing secret.

      **Important:** The signature is computed over `{timestamp}.{payload}`, not just the payload.

      ### Verification Steps

      1. Extract the `X-Webhook-Timestamp` and `X-Webhook-Signature` headers
      2. Get the **raw request body** (exact bytes, not parsed/re-serialized JSON)
      3. Compute: `HMAC-SHA256(secret, "{timestamp}.{raw_body}")`
      4. Compare your computed signature (hex-encoded) with `X-Webhook-Signature`

      ### JavaScript Example

      ```javascript
      const crypto = require('crypto');

      function verifyWebhookSignature(secret, rawBody, timestamp, signature) {
        const signedData = `${timestamp}.${rawBody}`;
        const expected = crypto.createHmac('sha256', secret).update(signedData).digest('hex');
        return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'));
      }

      // In your handler:
      const timestamp = req.headers['x-webhook-timestamp'];
      const signature = req.headers['x-webhook-signature'];
      const isValid = verifyWebhookSignature(secret, rawBody, timestamp, signature);
      ```

      ### Common Mistakes

      - **Missing timestamp:** Signing just the payload without prepending `{timestamp}.`
      - **Re-serialized JSON:** Using `JSON.stringify(parsedBody)` instead of the raw request body — this can change key order or whitespace, breaking the signature

      ## Delivery Guarantees & SLA

      | Guarantee | Value |
      |-----------|-------|
      | **Response timeout** | 10 seconds |
      | **Retry attempts** | 10 attempts per endpoint |
      | **Retry backoff** | Exponential with ±20% jitter, capped at 10 minutes |
      | **Total retry window** | ~25 minutes |
      | **Delivery model** | At-least-once (duplicates possible during retries) |

      ### Retry Behavior

      If your endpoint returns a non-2xx status code or times out, we will retry delivery with exponential backoff plus jitter:

      - **Attempt 1:** After ~2 seconds
      - **Attempt 2:** After ~4 seconds
      - **Attempt 3:** After ~8 seconds
      - **Attempt 4:** After ~16 seconds
      - **Attempt 5:** After ~32 seconds
      - **Attempt 6:** After ~64 seconds
      - **Attempt 7:** After ~128 seconds
      - **Attempt 8:** After ~256 seconds
      - **Attempt 9:** After ~512 seconds
      - **Attempt 10:** After ~600 seconds (capped at 10 minutes)

      After 10 failed attempts (~25 minutes total), the webhook is marked as failed for that endpoint.

      **Important:** Retries are per-endpoint. If you have multiple webhook subscriptions, a failure on one endpoint does not affect delivery to your other endpoints.

      ### What Triggers a Retry?

      - HTTP 5xx responses (server errors)
      - HTTP 429 (rate limited)
      - Connection timeout (>10 seconds)
      - Connection refused / network errors

      **Not retried:** HTTP 4xx responses (except 429), DNS resolution failures, and invalid hostnames are considered permanent failures and are not retried.

      ### Your Endpoint Should

      1. **Return `200` quickly** — respond within 10 seconds, process asynchronously if needed
      2. **Verify the HMAC signature** — reject requests with invalid signatures
      3. **Deduplicate using `event_id`** — the same event may arrive multiple times during retries
      4. **Be idempotent** — processing the same event twice should have no side effects
  - name: '2025-01-01'
    x-parent: Webhook Events
    description: |
      Webhook payload format for subscriptions created **before February 3, 2026**.

      This version uses `is_from_me` for direction and nests message fields under a `message` object.
  - name: '2026-02-03'
    x-parent: Webhook Events
    description: |
      Webhook payload format for subscriptions created **on or after February 3, 2026**.

      This version uses `direction: "inbound"/"outbound"` and places message fields at the top level of the `data` object.
paths:
  /v3/chats:
    post:
      operationId: createChat
      summary: Create a new chat
      description: |
        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. Text decorations only render for iMessage recipients.
        For SMS/RCS, text decorations are not applied.
      tags:
        - Chats
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateChatRequest'
            examples:
              chatWithTextMessage:
                summary: Text-only message
                value:
                  from: '+12052535597'
                  to:
                    - '+12052532136'
                  message:
                    parts:
                      - type: text
                        value: Hello! How can I help you today?
              chatWithMediaUrl:
                summary: Media via external URL (server downloads automatically)
                value:
                  from: '+12052535597'
                  to:
                    - '+12052532136'
                  message:
                    parts:
                      - type: text
                        value: Check out this image!
                      - type: media
                        url: https://images.dog.ceo/breeds/terrier-cairn/n02096177_13328.jpg
              chatWithPreuploadedMedia:
                summary: Media via pre-uploaded attachment (after POST /v3/attachments)
                value:
                  from: '+12052535597'
                  to:
                    - '+12052532136'
                  message:
                    parts:
                      - type: media
                        attachment_id: 550e8400-e29b-41d4-a716-446655440000
              chatWithConfettiEffect:
                summary: Message with confetti screen effect
                value:
                  from: '+12052535597'
                  to:
                    - '+12052532136'
                  message:
                    parts:
                      - type: text
                        value: Welcome aboard! 🎉
                    effect:
                      type: screen
                      name: confetti
              chatWithInvisibleInk:
                summary: Message with invisible ink bubble effect
                value:
                  from: '+12052535597'
                  to:
                    - '+12052532136'
                  message:
                    parts:
                      - type: text
                        value: 'Here''s a secret code: ABC123'
                    effect:
                      type: bubble
                      name: invisible
              chatWithTextDecorations:
                summary: Message with text decorations (iMessage only)
                value:
                  from: '+12052535597'
                  to:
                    - '+12052532136'
                  message:
                    parts:
                      - type: text
                        value: Hello world
                        text_decorations:
                          - range:
                              - 0
                              - 5
                            style: bold
                          - range:
                              - 6
                              - 11
                            animation: shake
      responses:
        '201':
          description: Chat created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateChatResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      operationId: listChats
      summary: List all chats
      description: |
        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`
        2. Response includes `next_cursor: "20"` (more results exist)
        3. Next request: `GET /v3/chats?from=%2B12223334444&limit=20&cursor=20`
        4. Response includes `next_cursor: null` (no more results)
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: from
          in: query
          required: false
          description: |
            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.
          schema:
            type: string
            pattern: ^\+?[1-9]\d{1,14}$
            example: '+13343284472'
        - name: to
          in: query
          required: false
          description: |
            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.
          schema:
            type: string
            example: '+13343284472'
        - name: limit
          in: query
          required: false
          description: Maximum number of chats to return per page
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
        - name: cursor
          in: query
          required: false
          description: |
            Pagination cursor from the previous response's `next_cursor` field.
            Omit this parameter for the first page of results.
          schema:
            type: string
            example: '20'
      responses:
        '200':
          description: List of chats retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListChatsResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/chats/{chatId}:
    get:
      operationId: getChat
      summary: Get a chat by ID
      description: Retrieve a chat by its unique identifier.
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        '200':
          description: Chat found and returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Chat'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      operationId: updateChat
      summary: Update a chat
      description: |
        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.
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateChatRequest'
            examples:
              updateName:
                summary: Update chat display name
                value:
                  display_name: Team Discussion
              updateIcon:
                summary: Update group chat icon
                value:
                  group_chat_icon: https://example.com/group-icon.png
              updateBoth:
                summary: Update both display name and icon
                value:
                  display_name: Team Discussion
                  group_chat_icon: https://example.com/group-icon.png
      responses:
        '202':
          description: Update request accepted and queued for processing
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: pending
                  chat_id:
                    type: string
                    format: uuid
                    example: 550e8400-e29b-41d4-a716-446655440000
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/chats/{chatId}/participants:
    post:
      operationId: addParticipant
      summary: Add a participant to a chat
      description: |
        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
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddParticipantRequest'
            examples:
              addUser:
                summary: Add a participant
                value:
                  handle: '+12052499136'
      responses:
        '202':
          description: Participant addition queued successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: accepted
                  trace_id:
                    type: string
                  message:
                    type: string
                    example: Participant addition queued
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: removeParticipant
      summary: Remove a participant from a chat
      description: |
        Remove a participant from an existing group chat.

        **Requirements:**
        - Group chats only
        - Must have 3+ participants after removal
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RemoveParticipantRequest'
            examples:
              removeUser:
                summary: Remove a participant
                value:
                  handle: '+12052499136'
      responses:
        '202':
          description: Participant removal queued successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: accepted
                  trace_id:
                    type: string
                  message:
                    type: string
                    example: Participant removal queued
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/chats/{chatId}/leave:
    post:
      operationId: leaveChat
      summary: Leave a group chat
      description: |
        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
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat to leave
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        '202':
          description: Leave group chat request queued successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: accepted
                  trace_id:
                    type: string
                  message:
                    type: string
                    example: Leave group chat queued
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/chats/{chatId}/typing:
    post:
      operationId: startTyping
      summary: Start typing indicator
      description: |
        Send a typing indicator to show that someone is typing in the chat.

        **Note:** Group chat typing indicators are not currently supported.
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        '204':
          description: Typing indicator started successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: stopTyping
      summary: Stop typing indicator
      description: |
        Stop the typing indicator for the chat.

        **Note:** Typing indicators are automatically stopped when a message is sent,
        so calling this endpoint after sending a message is unnecessary.

        **Note:** Group chat typing indicators are not currently supported.
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        '204':
          description: Typing indicator stopped successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/chats/{chatId}/read:
    post:
      operationId: markChatAsRead
      summary: Mark chat as read
      description: |
        Mark all messages in a chat as read.
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Chat marked as read successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/chats/{chatId}/share_contact_card:
    post:
      operationId: shareContactWithChat
      summary: Share your contact card with a chat
      description: |
        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 on the [Linq dashboard](https://dashboard.linqapp.com/contact-cards).
      tags:
        - Chats
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Contact shared successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/chats/{chatId}/messages:
    post:
      operationId: sendMessageToChat
      summary: Send a message to an existing chat
      description: |
        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. Text decorations only render for iMessage recipients.
        For SMS/RCS, text decorations are not applied.
      tags:
        - Messages
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMessageToChatRequest'
            examples:
              simple_text:
                summary: Simple text message
                value:
                  message:
                    parts:
                      - type: text
                        value: Hello, world!
              with_confetti:
                summary: Message with confetti effect
                value:
                  message:
                    parts:
                      - type: text
                        value: Congratulations! 🎉
                    effect:
                      type: screen
                      name: confetti
              with_slam:
                summary: Message with slam bubble effect
                value:
                  message:
                    parts:
                      - type: text
                        value: BOOM!
                    effect:
                      type: bubble
                      name: slam
              with_invisible_ink:
                summary: Message with invisible ink effect
                value:
                  message:
                    parts:
                      - type: text
                        value: This is a secret message
                    effect:
                      type: bubble
                      name: invisible
              with_text_decorations:
                summary: Message with text decorations (iMessage only)
                value:
                  message:
                    parts:
                      - type: text
                        value: Hello world
                        text_decorations:
                          - range:
                              - 0
                              - 5
                            style: bold
                          - range:
                              - 6
                              - 11
                            animation: shake
      responses:
        '202':
          description: Message accepted for delivery
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendMessageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      operationId: getMessages
      summary: Get messages from a chat
      description: |
        Retrieve messages from a specific chat with pagination support.
      tags:
        - Messages
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
        - name: cursor
          in: query
          description: Pagination cursor from previous next_cursor response
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of messages to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        '200':
          description: Messages retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetMessagesResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/messages/{messageId}/thread:
    get:
      operationId: getMessageThread
      summary: Get all messages in a thread
      description: |
        Retrieve all messages in a conversation thread. Given any message ID in the thread,
        returns the originator message and all replies in chronological order.

        If the message is not part of a thread, returns just that single message.

        Supports pagination and configurable ordering.
      tags:
        - Messages
      security:
        - BearerAuth: []
      parameters:
        - name: messageId
          in: path
          required: true
          description: ID of any message in the thread (can be originator or any reply)
          schema:
            type: string
            format: uuid
            example: 69a37c7d-af4f-4b5e-af42-e28e98ce873a
        - name: cursor
          in: query
          description: Pagination cursor from previous next_cursor response
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of messages to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: order
          in: query
          description: Sort order for messages (asc = oldest first, desc = newest first)
          schema:
            type: string
            enum:
              - asc
              - desc
            default: asc
      responses:
        '200':
          description: Thread retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetThreadResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/chats/{chatId}/voicememo:
    post:
      operationId: sendVoiceMemoToChat
      summary: Send a voice memo to a chat
      description: |
        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)
      tags:
        - Messages
      security:
        - BearerAuth: []
      parameters:
        - name: chatId
          in: path
          required: true
          description: Unique identifier of the chat
          schema:
            type: string
            format: uuid
            example: f19ee7b8-8533-4c5c-83ec-4ef8d6d1ddbd
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendVoiceMemoToChatRequest'
            example:
              voice_memo_url: https://example.com/voice-memo.m4a
      responses:
        '202':
          description: Voice memo accepted for delivery
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendVoiceMemoToChatResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '413':
          description: Voice memo file too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  status: 413
                  code: 5001
                  message: Voice memo file too large - maximum size is 10MB
                success: false
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/messages/{messageId}:
    get:
      operationId: getMessage
      summary: Get a message by ID
      description: |
        Retrieve a specific message by its ID. This endpoint returns the full message
        details including text, attachments, reactions, and metadata.
      tags:
        - Messages
      security:
        - BearerAuth: []
      parameters:
        - name: messageId
          in: path
          required: true
          description: Unique identifier of the message to retrieve
          schema:
            type: string
            format: uuid
            example: 69a37c7d-af4f-4b5e-af42-e28e98ce873a
      responses:
        '200':
          description: Message found and returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    patch:
      operationId: editMessage
      summary: Edit the content of a message part
      description: Edit the text content of a specific part of a previously sent message.
      tags:
        - Messages
      security:
        - BearerAuth: []
      parameters:
        - name: messageId
          in: path
          required: true
          description: Unique identifier of the message to edit
          schema:
            type: string
            format: uuid
            example: 69a37c7d-af4f-4b5e-af42-e28e98ce873a
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EditMessageRequest'
            examples:
              editMessage:
                summary: Edit the first part of a message
                value:
                  part_index: 0
                  text: This is the edited message content
      responses:
        '200':
          description: Message updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: deleteMessage
      summary: Delete a message from system
      description: |
        Deletes a message from the Linq API only. This does NOT unsend or remove the message
        from the actual chat - recipients will still see the message.

        Use this endpoint to remove messages from your records and prevent them from appearing
        in API responses.
      tags:
        - Messages
      security:
        - BearerAuth: []
      parameters:
        - name: messageId
          in: path
          required: true
          description: Unique identifier of the message to delete
          schema:
            type: string
            format: uuid
            example: 69a37c7d-af4f-4b5e-af42-e28e98ce873a
      responses:
        '204':
          description: Message deleted successfully (no content returned)
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/messages/{messageId}/reactions:
    post:
      operationId: sendReaction
      summary: Add or remove a reaction to a message
      description: |
        Add or remove emoji reactions to messages. Reactions let users express
        their response to a message without sending a new message.

        **Supported Reactions:**
        - love ❤️
        - like 👍
        - dislike 👎
        - laugh 😂
        - emphasize ‼️
        - question ❓
        - custom - any emoji (use `custom_emoji` field to specify)
      tags:
        - Messages
      security:
        - BearerAuth: []
      parameters:
        - name: messageId
          in: path
          required: true
          description: Unique identifier of the message to react to
          schema:
            type: string
            format: uuid
            example: 69a37c7d-af4f-4b5e-af42-e28e98ce873a
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendReactionRequest'
            examples:
              addLove:
                summary: Add a love reaction
                value:
                  operation: add
                  type: love
              removeLike:
                summary: Remove a like reaction
                value:
                  operation: remove
                  type: like
              reactToMessagePart:
                summary: React to a specific message part
                value:
                  operation: add
                  type: laugh
                  part_index: 1
      responses:
        '202':
          description: Reaction queued successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: accepted
                  trace_id:
                    type: string
                  message:
                    type: string
                    example: Reaction processed
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/attachments:
    post:
      operationId: requestUpload
      summary: Pre-upload a file
      description: |
        **This endpoint is optional.** You can send media by simply providing a URL in your
        message's media part — no pre-upload required. Use this endpoint only when you want
        to upload a file ahead of time for reuse or latency optimization.

        Returns a presigned upload URL and a permanent `attachment_id` you can reference
        in future messages.

        ## Step 1: Request an upload URL

        Call this endpoint with file metadata:

        ```json
        POST /v3/attachments
        {
          "filename": "photo.jpg",
          "content_type": "image/jpeg",
          "size_bytes": 1024000
        }
        ```

        The response includes an `upload_url` (valid for 15 minutes) and a permanent `attachment_id`.

        ## Step 2: Upload the file

        Make a PUT request to the `upload_url` with the raw file bytes as the request body.
        You **must** include all headers from `required_headers` exactly as returned — the presigned URL
        is signed with these values and S3 will reject the upload if they don't match.

        The request body is the binary file content — **not** JSON, **not** multipart form data.
        The file must equal `size_bytes` bytes (the value you declared in step 1).

        ```bash
        curl -X PUT "<upload_url from step 1>" \
          -H "Content-Type: image/jpeg" \
          -H "Content-Length: 1024000" \
          --data-binary @photo.jpg
        ```

        ## Step 3: Send a message with the attachment

        Reference the `attachment_id` in a media part. The ID never expires — use it in as many messages as you want.

        ```json
        POST /v3/chats
        {
          "from": "+15559876543",
          "to": ["+15551234567"],
          "message": {
            "parts": [
              { "type": "media", "attachment_id": "<attachment_id from step 1>" }
            ]
          }
        }
        ```

        ## When to use this instead of a URL in the media part

        - Sending the same file to multiple recipients (avoids re-downloading each time)
        - Large files where you want to separate upload from message send
        - Latency-sensitive sends where the file should already be stored

        If you just need to send a file once, skip all of this and pass a `url` directly in the media part instead.

        **File Size Limit:** 100MB

        **Unsupported Types:** WebP, SVG, FLAC, OGG, and executable files are explicitly rejected.
      tags:
        - Attachments
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RequestUploadRequest'
            examples:
              imageUpload:
                summary: Request upload for an image
                value:
                  filename: photo.jpg
                  content_type: image/jpeg
                  size_bytes: 1024000
              videoUpload:
                summary: Request upload for a video
                value:
                  filename: video.mp4
                  content_type: video/mp4
                  size_bytes: 50000000
      responses:
        '200':
          description: Upload URL generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RequestUploadResult'
              examples:
                presignedUrlResponse:
                  summary: Presigned upload URL ready
                  value:
                    attachment_id: 550e8400-e29b-41d4-a716-446655440000
                    upload_url: https://uploads.linqapp.com/attachments/550e8400?X-Amz-Algorithm=AWS4-HMAC-SHA256&...
                    download_url: https://cdn.linqapp.com/uploads/partner-id/550e8400/photo.jpg
                    http_method: PUT
                    expires_at: '2024-01-15T10:45:00Z'
                    required_headers:
                      Content-Type: image/jpeg
                      Content-Length: '1024000'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/attachments/{attachmentId}:
    get:
      operationId: getAttachment
      summary: Get attachment metadata
      description: |
        Retrieve metadata for a specific attachment including its status,
        file information, and URLs for downloading.
      tags:
        - Attachments
      security:
        - BearerAuth: []
      parameters:
        - name: attachmentId
          in: path
          required: true
          description: Unique identifier of the attachment
          schema:
            type: string
            format: uuid
            example: abc12345-1234-5678-9abc-def012345678
      responses:
        '200':
          description: Attachment found and returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Attachment'
              example:
                id: 550e8400-e29b-41d4-a716-446655440000
                filename: photo.jpg
                content_type: image/jpeg
                size_bytes: 1024000
                status: complete
                download_url: https://cdn.linqapp.com/attachments/550e8400-e29b-41d4-a716-446655440000/photo.jpg
                created_at: '2024-01-15T10:30:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/phonenumbers:
    get:
      operationId: listPhoneNumbersDeprecated
      summary: List phone numbers (deprecated)
      deprecated: true
      description: |
        **Deprecated.** Use `GET /v3/phone_numbers` instead.
      tags:
        - Phone Numbers
      security:
        - BearerAuth: []
      x-internal: true
      responses:
        '200':
          description: Phone numbers retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListPhoneNumbersResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/phone_numbers:
    get:
      operationId: listPhoneNumbers
      summary: List phone numbers
      description: |
        Returns all phone numbers assigned to the authenticated partner.
        Use this endpoint to discover which phone numbers are available for
        use as the `from` field when creating a chat, listing chats, or sending a voice memo.
      tags:
        - Phone Numbers
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Phone numbers retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListPhoneNumbersResultV2'
              example:
                phone_numbers:
                  - id: 550e8400-e29b-41d4-a716-446655440000
                    phone_number: '+12025551234'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/webhook-events:
    get:
      operationId: listWebhookEvents
      summary: List available webhook event types
      description: |
        Returns all available webhook event types that can be subscribed to.
        Use this endpoint to discover valid values for the `subscribed_events`
        field when creating or updating webhook subscriptions.
      tags:
        - Webhooks
      security:
        - BearerAuth: []
      responses:
        '200':
          description: List of available webhook event types
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEventsResult'
              example:
                events:
                  - message.sent
                  - message.received
                  - message.read
                  - message.delivered
                  - message.failed
                  - reaction.added
                  - reaction.removed
                  - participant.added
                  - participant.removed
                  - chat.created
                  - chat.group_name_updated
                  - chat.group_icon_updated
                  - chat.group_name_update_failed
                  - chat.group_icon_update_failed
                  - chat.typing_indicator.started
                  - chat.typing_indicator.stopped
                  - message.edited
                  - phone_number.status_updated
                doc_url: https://apidocs.linqapp.com/documentation/webhook-events
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/webhook-subscriptions:
    post:
      operationId: createWebhookSubscription
      summary: Create a new webhook subscription
      description: |
        Create a new webhook subscription to receive events at a target URL.
        Upon creation, a signing secret is generated for verifying webhook
        authenticity. **Store this secret securely — it cannot be retrieved later.**

        **Phone Number Filtering:**
        - Optionally specify `phone_numbers` to only receive events for specific lines
        - If omitted, events from all phone numbers are delivered (default behavior)
        - Use multiple subscriptions with different `phone_numbers` to route different lines to different endpoints
        - Each `target_url` can only be used once per account. To route different
          lines to different destinations, use a unique URL per subscription
          (e.g., append a query parameter: `https://example.com/webhook?line=1`)

        **Webhook Delivery:**
        - Events are sent via HTTP POST to the target URL
        - Each request includes `X-Webhook-Signature` and `X-Webhook-Timestamp` headers
        - Signature is HMAC-SHA256 over `{timestamp}.{payload}` — see [Webhook Events](/docs/webhook-events) for verification details
        - Failed deliveries (5xx, 429, network errors) are retried up to 10 times over ~25 minutes with exponential backoff
        - Client errors (4xx except 429) are not retried
      tags:
        - Webhooks
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookSubscriptionRequest'
            examples:
              messageEvents:
                summary: Subscribe to message events
                value:
                  target_url: https://webhooks.example.com/linq/events
                  subscribed_events:
                    - message.sent
                    - message.delivered
                    - message.read
              messageEventsWithVersion:
                summary: Subscribe with explicit webhook version
                value:
                  target_url: https://webhooks.example.com/linq/events?version=2026-02-03
                  subscribed_events:
                    - message.sent
                    - message.delivered
                    - message.read
              allEvents:
                summary: Subscribe to all events
                value:
                  target_url: https://webhooks.example.com/linq/events
                  subscribed_events:
                    - message.sent
                    - message.received
                    - message.delivered
                    - message.read
                    - message.failed
                    - message.edited
                    - reaction.added
                    - reaction.removed
                    - participant.added
                    - participant.removed
                    - chat.created
                    - chat.group_name_updated
                    - chat.group_icon_updated
                    - chat.group_name_update_failed
                    - chat.group_icon_update_failed
                    - chat.typing_indicator.started
                    - chat.typing_indicator.stopped
                    - phone_number.status_updated
              perLineSubscription:
                summary: Subscribe to events for specific phone numbers only
                value:
                  target_url: https://webhooks.example.com/linq/line1
                  subscribed_events:
                    - message.sent
                    - message.received
                  phone_numbers:
                    - '+12025551234'
      responses:
        '201':
          description: Webhook subscription created successfully. The signing_secret is only returned in this response - store it securely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionCreatedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      operationId: listWebhookSubscriptions
      summary: List all webhook subscriptions
      description: |
        Retrieve all webhook subscriptions for the authenticated partner.
        Returns a list of active and inactive subscriptions with their
        configuration and status.
      tags:
        - Webhooks
      security:
        - BearerAuth: []
      responses:
        '200':
          description: List of webhook subscriptions retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListWebhookSubscriptionsResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/webhook-subscriptions/{subscriptionId}:
    get:
      operationId: getWebhookSubscription
      summary: Get a webhook subscription by ID
      description: |
        Retrieve details for a specific webhook subscription including its
        target URL, subscribed events, and current status.
      tags:
        - Webhooks
      security:
        - BearerAuth: []
      parameters:
        - name: subscriptionId
          in: path
          required: true
          description: Unique identifier of the webhook subscription
          schema:
            type: string
            example: b2c3d4e5-f6a7-8901-bcde-f23456789012
      responses:
        '200':
          description: Webhook subscription found and returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      operationId: updateWebhookSubscription
      summary: Update a webhook subscription
      description: |
        Update an existing webhook subscription. You can modify the target URL,
        subscribed events, or activate/deactivate the subscription.

        **Note:** The signing secret cannot be changed via this endpoint.
      tags:
        - Webhooks
      security:
        - BearerAuth: []
      parameters:
        - name: subscriptionId
          in: path
          required: true
          description: Unique identifier of the webhook subscription
          schema:
            type: string
            example: b2c3d4e5-f6a7-8901-bcde-f23456789012
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWebhookSubscriptionRequest'
            examples:
              updateURL:
                summary: Update target URL
                value:
                  target_url: https://webhooks.example.com/linq/events
              updateEvents:
                summary: Update subscribed events
                value:
                  subscribed_events:
                    - message.sent
                    - message.delivered
              deactivate:
                summary: Deactivate subscription
                value:
                  is_active: false
              filterByPhoneNumbers:
                summary: Filter to specific phone numbers
                value:
                  phone_numbers:
                    - '+12025551234'
                    - '+12025559876'
              clearPhoneFilter:
                summary: Remove phone number filter (receive all events)
                value:
                  phone_numbers: []
      responses:
        '200':
          description: Webhook subscription updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      operationId: deleteWebhookSubscription
      summary: Delete a webhook subscription
      description: Delete a webhook subscription.
      tags:
        - Webhooks
      security:
        - BearerAuth: []
      parameters:
        - name: subscriptionId
          in: path
          required: true
          description: Unique identifier of the webhook subscription
          schema:
            type: string
            example: b2c3d4e5-f6a7-8901-bcde-f23456789012
      responses:
        '204':
          description: Webhook subscription deleted successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/capability/check_imessage:
    post:
      operationId: checkImessageCapability
      summary: Check iMessage capability
      description: |
        Check whether a recipient address (phone number or email) is reachable via iMessage.
      tags:
        - Capability Checks
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HandleCheckRequest'
            example:
              address: '+15551234567'
      responses:
        '200':
          description: iMessage capability check completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HandleCheckResponse'
              example:
                address: '+15551234567'
                available: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v3/capability/check_rcs:
    post:
      operationId: checkRcsCapability
      summary: Check RCS capability
      description: |
        Check whether a recipient address (phone number) supports RCS messaging.
      tags:
        - Capability Checks
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HandleCheckRequest'
            example:
              address: '+15551234567'
      responses:
        '200':
          description: RCS capability check completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HandleCheckResponse'
              example:
                address: '+15551234567'
                available: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /v3/contact_card:
    get:
      operationId: getContactCard
      summary: Get contact cards
      description: |
        Returns the contact card for a specific phone number, or all contact cards for the
        authenticated partner if no `phone_number` is provided.
      tags:
        - Contact Card
      security:
        - BearerAuth: []
      parameters:
        - name: phone_number
          in: query
          required: false
          description: E.164 phone number to filter by. If omitted, all my cards for the partner are returned.
          schema:
            type: string
            example: '+15551234567'
      responses:
        '200':
          description: Contact card(s) returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetContactCardResponse'
              example:
                contact_cards:
                  - phone_number: '+15551234567'
                    first_name: John
                    last_name: Doe
                    image_url: https://cdn.linqapp.com/contact-card/example.jpg
                    is_active: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      operationId: setupContactCard
      summary: Setup contact card
      description: |
        Creates a contact card for a phone number.

        The contact card is stored in an inactive state first. Once it's applied successfully,
        it is activated and `is_active` is returned as `true`. On failure, `is_active` is `false`.
      tags:
        - Contact Card
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetContactCardRequest'
            example:
              phone_number: '+15551234567'
              first_name: John
              last_name: Doe
              image_url: https://cdn.linqapp.com/contact-card/example.jpg
      responses:
        '200':
          description: Contact card created/updated and sync attempted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SetContactCardResponse'
              example:
                phone_number: '+15551234567'
                first_name: John
                last_name: Doe
                image_url: https://cdn.linqapp.com/contact-card/example.jpg
                is_active: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
    patch:
      operationId: updateContactCard
      summary: Update contact card
      description: |
        Partially updates an existing active contact card for a phone number.

        Fetches the current active contact card and merges the provided fields.
        Only fields present in the request body are updated; omitted fields retain their existing values.

        Requires an active contact card to exist for the phone number.
      tags:
        - Contact Card
      security:
        - BearerAuth: []
      parameters:
        - name: phone_number
          in: query
          required: true
          description: E.164 phone number of the contact card to update
          schema:
            type: string
            example: '+15551234567'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateContactCardRequest'
            example:
              first_name: John
              last_name: Doe
              image_url: https://cdn.linqapp.com/contact-card/example.jpg
      responses:
        '200':
          description: Contact card updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SetContactCardResponse'
              example:
                phone_number: '+15551234567'
                first_name: John
                last_name: Doe
                image_url: https://cdn.linqapp.com/contact-card/example.jpg
                is_active: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
webhooks:
  message.sent.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookMessageSentV2026
      summary: Message sent
      description: |
        Triggered when a message has been successfully sent from your phone number.
        This confirms the message has been sent, but potentially has not yet been delivered.

        **Timestamps:** `sent_at` is set, `delivered_at` and `read_at` are null.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageSentWebhookV2'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: message.sent
              event_id: e20feb41-7f67-43f0-89c8-a985cff3b568
              created_at: '2026-02-05T19:52:18.101373886Z'
              trace_id: 2eff5df5c6f688733c007523c4d61cd9
              partner_id: your-partner-id
              data:
                chat:
                  id: 0c961e93-e7bf-4db2-bf7b-ea06826bcab4
                  is_group: false
                  owner_handle:
                    handle: '+12025551234'
                    id: 8d79532a-f529-4244-a5cf-d443de051434
                    is_me: true
                    joined_at: '2026-01-21T21:59:45.191571Z'
                    left_at: null
                    service: iMessage
                    status: active
                id: 347d62c2-2170-4754-8d30-c76d0c727d96
                idempotency_key: null
                direction: outbound
                sender_handle:
                  handle: '+12025551234'
                  id: 8d79532a-f529-4244-a5cf-d443de051434
                  is_me: true
                  joined_at: '2026-01-21T21:59:45.191571Z'
                  left_at: null
                  service: iMessage
                  status: active
                parts:
                  - type: text
                    value: Hello from Linq!
                  - filename: photo.jpg
                    id: f13dda7d-ecac-49eb-b3fe-16fe286abf19
                    mime_type: image/jpeg
                    size_bytes: 245678
                    type: media
                    url: https://cdn.linqapp.com/attachments/a1b2c3d4/photo.jpg?signature=...
                effect: null
                sent_at: '2026-02-05T19:52:17.219Z'
                delivered_at: null
                read_at: null
                service: iMessage
                preferred_service: null
      responses:
        '200':
          description: Webhook received successfully
  message.received.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookMessageReceivedV2026
      summary: Message received
      description: |
        Triggered when an incoming message is received on your phone number.
        Contains the full message content including any attachments.

        **Timestamps:** `sent_at` is set, `delivered_at` and `read_at` are null.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageReceivedWebhookV2'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: message.received
              event_id: 2915e81c-5068-4796-ace2-21d2c94ad298
              created_at: '2026-02-05T19:31:13.736444093Z'
              trace_id: 8af9171a45022df2eb74ba4e4c83be0f
              partner_id: your-partner-id
              data:
                chat:
                  id: 8f392755-6865-4b18-880a-227f9d8b458f
                  is_group: false
                  owner_handle:
                    handle: '+12025551234'
                    id: 6d6c617f-187a-4dcd-a0d5-988347a8c092
                    is_me: true
                    joined_at: '2026-01-04T05:48:51.321469Z'
                    left_at: null
                    service: iMessage
                    status: active
                id: 89e3566e-1d13-49e5-a8ee-48490d5bfeb7
                direction: inbound
                sender_handle:
                  handle: '+12025559876'
                  id: e604375a-5913-483a-8278-c631e8f0ffda
                  is_me: false
                  joined_at: '2026-01-04T05:48:51.321469Z'
                  left_at: null
                  service: iMessage
                  status: active
                parts:
                  - type: text
                    value: Hello!
                effect: null
                reply_to: null
                sent_at: '2026-02-05T19:31:13.074Z'
                delivered_at: null
                read_at: null
                service: iMessage
      responses:
        '200':
          description: Webhook received successfully
  message.read.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookMessageReadV2026
      summary: Message read
      description: |
        Triggered when a sent message has been read by the recipient.
        Only available for iMessage conversations with read receipts enabled.

        **Timestamps:** `sent_at`, `delivered_at`, and `read_at` are all set.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageReadWebhookV2'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: message.read
              event_id: 8fd42065-b998-482a-93b3-da855f8dad17
              created_at: '2026-02-05T19:13:58.833366566Z'
              trace_id: cbb93c08fa1a3f3c4c2efc161d67f36d
              partner_id: your-partner-id
              data:
                chat:
                  id: 24e33345-e6cf-4f50-9d35-1d7fde8c9818
                  is_group: false
                  owner_handle:
                    handle: '+12025551234'
                    id: d31678e9-0442-48fd-b7ed-c898d245dd15
                    is_me: true
                    joined_at: '2026-01-18T03:38:41.442254Z'
                    left_at: null
                    service: iMessage
                    status: active
                id: dc6d3f68-90df-48f0-a504-e65f239a383c
                idempotency_key: null
                direction: outbound
                sender_handle:
                  handle: '+12025551234'
                  id: d31678e9-0442-48fd-b7ed-c898d245dd15
                  is_me: true
                  joined_at: '2026-01-18T03:38:41.442254Z'
                  left_at: null
                  service: iMessage
                  status: active
                parts:
                  - type: text
                    value: Hello world!
                effect: null
                sent_at: '2026-02-05T19:13:57.814Z'
                delivered_at: '2026-02-05T19:13:57.948Z'
                read_at: '2026-02-05T19:13:58.177Z'
                service: iMessage
                preferred_service: null
      responses:
        '200':
          description: Webhook received successfully
  message.delivered.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookMessageDeliveredV2026
      summary: Message delivered
      description: |
        Triggered when a sent message has been delivered to the recipient's device.
        This confirms the message reached the recipient, but does not indicate it was read.
        Only available for iMessage conversations with delivery receipts.

        **Timestamps:** `sent_at` and `delivered_at` are set, `read_at` is null.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageDeliveredWebhookV2'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: message.delivered
              event_id: 67c4ad39-e9b0-47f6-82f8-64bdd8ceafa6
              created_at: '2026-02-05T19:52:22.593689073Z'
              trace_id: abde7f6248fba00f97e8c7dc4782d7e0
              partner_id: your-partner-id
              data:
                chat:
                  id: 0c961e93-e7bf-4db2-bf7b-ea06826bcab4
                  is_group: false
                  owner_handle:
                    handle: '+12025551234'
                    id: 8d79532a-f529-4244-a5cf-d443de051434
                    is_me: true
                    joined_at: '2026-01-21T21:59:45.191571Z'
                    left_at: null
                    service: iMessage
                    status: active
                id: 347d62c2-2170-4754-8d30-c76d0c727d96
                idempotency_key: null
                direction: outbound
                sender_handle:
                  handle: '+12025551234'
                  id: 8d79532a-f529-4244-a5cf-d443de051434
                  is_me: true
                  joined_at: '2026-01-21T21:59:45.191571Z'
                  left_at: null
                  service: iMessage
                  status: active
                parts:
                  - type: text
                    value: Hello from Linq!
                  - filename: photo.gif
                    id: f13dda7d-ecac-49eb-b3fe-16fe286abf19
                    mime_type: image/gif
                    size_bytes: 2776819
                    type: media
                    url: https://cdn.linqapp.com/attachments/f13dda7d/photo.gif?signature=...
                effect: null
                sent_at: '2026-02-05T19:52:17.219Z'
                delivered_at: '2026-02-05T19:52:22.291Z'
                read_at: null
                service: iMessage
                preferred_service: null
      responses:
        '200':
          description: Webhook received successfully
  message.failed.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookMessageFailedV2026
      summary: Message delivery failed
      description: |
        Triggered when a message fails to be delivered. This can happen due to:
        - Request timeout (message expired before being processed)
        - Upstream service error (processing failed after retries)
        - Request cancelled (message was explicitly cancelled)
        - Service unavailable (no service available to process the request)

        Note: The original message content may not be available if the message
        expired before being consumed by a processor.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageFailedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: message.failed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                message_id: 550e8400-e29b-41d4-a716-446655440001
                code: 4001
                reason: Delivery failed
                failed_at: '2025-11-23T17:35:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  reaction.added.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookReactionAddedV2026
      summary: Reaction added
      description: |
        Triggered when a reaction (tapback) is added to a message.
        Includes standard reactions like "loved", "liked", "disliked", etc.
        and custom emoji reactions.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReactionAddedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: reaction.added
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                message_id: 550e8400-e29b-41d4-a716-446655440001
                part_index: 0
                reaction_type: love
                custom_emoji: null
                is_from_me: false
                from: '+14155559876'
                from_handle:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: null
                service: iMessage
                reacted_at: '2025-11-23T17:35:00.000Z'
                sticker: null
      responses:
        '200':
          description: Webhook received successfully
  reaction.removed.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookReactionRemovedV2026
      summary: Reaction removed
      description: |
        Triggered when a reaction (tapback) is removed from a message.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReactionRemovedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: reaction.removed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                message_id: 550e8400-e29b-41d4-a716-446655440001
                part_index: 0
                reaction_type: love
                custom_emoji: null
                is_from_me: false
                from: '+14155559876'
                from_handle:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: null
                service: iMessage
                reacted_at: '2025-11-23T17:35:00.000Z'
                sticker: null
      responses:
        '200':
          description: Webhook received successfully
  participant.added.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookParticipantAddedV2026
      summary: Participant added
      description: |
        Triggered when a new participant is added to a group chat.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ParticipantAddedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: participant.added
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                handle: '+14155559876'
                participant:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:40:00.000Z'
                  left_at: null
                added_at: '2025-11-23T17:40:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  participant.removed.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookParticipantRemovedV2026
      summary: Participant removed
      description: |
        Triggered when a participant is removed from a group chat.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ParticipantRemovedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: participant.removed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                handle: '+14155559876'
                participant:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: removed
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: '2025-11-23T17:45:00.000Z'
                removed_at: '2025-11-23T17:45:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.group_name_updated.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookChatGroupNameUpdatedV2026
      summary: Group name updated
      description: |
        Triggered when a group chat's display name is updated.
        A null `new_value` indicates the name was removed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatGroupNameUpdatedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: chat.group_name_updated
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                old_value: Old Group Name
                new_value: New Group Name
                changed_by_handle:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: null
                updated_at: '2025-11-23T17:50:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.group_icon_updated.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookChatGroupIconUpdatedV2026
      summary: Group icon updated
      description: |
        Triggered when a group chat's icon is updated.
        A null `new_value` indicates the icon was removed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatGroupIconUpdatedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: chat.group_icon_updated
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                old_value: https://example.com/old-icon.png
                new_value: https://example.com/new-icon.png
                changed_by_handle:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: null
                updated_at: '2025-11-23T17:50:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.group_name_update_failed.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookChatGroupNameUpdateFailedV2026
      summary: Group name update failed
      description: Triggered when a group chat name update request fails.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatGroupNameUpdateFailedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: chat.group_name_update_failed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                error_code: 3007
                failed_at: '2025-11-23T17:55:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.group_icon_update_failed.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookChatGroupIconUpdateFailedV2026
      summary: Group icon update failed
      description: Triggered when a group chat icon update request fails.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatGroupIconUpdateFailedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: chat.group_icon_update_failed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                error_code: 3007
                failed_at: '2025-11-23T17:55:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.created.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookChatCreatedV2026
      summary: Chat created
      description: Triggered when a new chat is created.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCreatedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: chat.created
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                id: 550e8400-e29b-41d4-a716-446655440000
                display_name: +14155551234, +14155559876
                service: iMessage
                handles:
                  - id: 550e8400-e29b-41d4-a716-446655440010
                    handle: '+14155551234'
                    is_me: true
                    service: iMessage
                    status: active
                    joined_at: '2025-11-23T17:30:00.000Z'
                    left_at: null
                  - id: 550e8400-e29b-41d4-a716-446655440011
                    handle: '+14155559876'
                    is_me: false
                    service: iMessage
                    status: active
                    joined_at: '2025-11-23T17:30:00.000Z'
                    left_at: null
                is_group: false
                created_at: '2025-11-23T17:30:00.000Z'
                updated_at: '2025-11-23T17:30:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.typing_indicator.started.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookChatTypingIndicatorStartedV2026
      summary: Typing indicator started
      description: Triggered when a participant starts typing in a chat.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatTypingIndicatorStartedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: chat.typing_indicator.started
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
      responses:
        '200':
          description: Webhook received successfully
  chat.typing_indicator.stopped.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookChatTypingIndicatorStoppedV2026
      summary: Typing indicator stopped
      description: Triggered when a participant stops typing in a chat.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatTypingIndicatorStoppedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: chat.typing_indicator.stopped
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
      responses:
        '200':
          description: Webhook received successfully
  message.edited.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookMessageEditedV2026
      summary: Message edited
      description: |
        Triggered when a message part is edited.

        Only text parts can be edited. Only available for subscriptions using
        `webhook_version: "2026-02-03"` — this event is not delivered to v2025 subscriptions.

        **Timestamps:** `edited_at` reflects when the edit occurred.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageEditedWebhookV2'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: message.edited
              event_id: c3d4e5f6-a7b8-9012-cdef-345678901234
              created_at: '2026-03-05T02:12:46.501Z'
              trace_id: d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6
              partner_id: your-partner-id
              data:
                chat:
                  id: 8f392755-6865-4b18-880a-227f9d8b458f
                  is_group: false
                  owner_handle:
                    handle: '+12025551234'
                    id: 6d6c617f-187a-4dcd-a0d5-988347a8c092
                    is_me: true
                    joined_at: '2026-01-04T05:48:51.321469Z'
                    left_at: null
                    service: iMessage
                    status: active
                id: 89e3566e-1d13-49e5-a8ee-48490d5bfeb7
                direction: outbound
                sender_handle:
                  handle: '+12025551234'
                  id: 6d6c617f-187a-4dcd-a0d5-988347a8c092
                  is_me: true
                  joined_at: '2026-01-04T05:48:51.321469Z'
                  left_at: null
                  service: iMessage
                  status: active
                part:
                  index: 0
                  text: This is the edited message content
                edited_at: '2026-03-05T02:12:46.487Z'
      responses:
        '200':
          description: Webhook received successfully
  phone_number.status_updated.v2026:
    post:
      tags:
        - '2026-02-03'
      operationId: webhookPhoneNumberStatusUpdatedV2026
      summary: Phone number status updated
      description: |
        Triggered when a phone number's service status changes from active to flagged or vice versa.
        Use this to monitor phone number health and availability.

        **Status values:**
        - `ACTIVE` — Phone number is healthy and can send/receive messages
        - `FLAGGED` — Phone number has been flagged and may have limited functionality
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PhoneNumberStatusUpdatedWebhook'
            example:
              api_version: v3
              webhook_version: '2026-02-03'
              event_type: phone_number.status_updated
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2026-02-18T18:35:05.363Z'
              trace_id: b66e67c5c6b2c20e41d53c51698db27a
              partner_id: your-partner-id
              data:
                phone_number: '+12025551234'
                previous_status: ACTIVE
                new_status: FLAGGED
                changed_at: '2026-02-18T18:35:05.000Z'
      responses:
        '200':
          description: Webhook received successfully
  message.sent.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookMessageSentV2025
      summary: Message sent
      description: |
        Triggered when a message has been successfully sent from your phone number.
        This confirms the message has been sent, but potentially has not yet been delivered.

        **Timestamps:** `sent_at` is set, `delivered_at` and `read_at` are null.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageSentWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: message.sent
              event_id: e20feb41-7f67-43f0-89c8-a985cff3b568
              created_at: '2026-02-05T19:52:18.101373886Z'
              trace_id: 2eff5df5c6f688733c007523c4d61cd9
              partner_id: your-partner-id
              data:
                chat_id: 0c961e93-e7bf-4db2-bf7b-ea06826bcab4
                from: '+12025551234'
                from_handle:
                  handle: '+12025551234'
                  id: 8d79532a-f529-4244-a5cf-d443de051434
                  is_me: true
                  joined_at: '2026-01-21T21:59:45.191571Z'
                  left_at: null
                  service: iMessage
                  status: active
                idempotency_key: null
                is_from_me: true
                is_group: false
                message:
                  created_at: '2026-02-05T19:52:17.041183Z'
                  delivered_at: null
                  id: 347d62c2-2170-4754-8d30-c76d0c727d96
                  is_delivered: false
                  is_read: false
                  parts:
                    - type: text
                      value: Hello from Linq!
                    - filename: photo.gif
                      id: f13dda7d-ecac-49eb-b3fe-16fe286abf19
                      mime_type: image/gif
                      size_bytes: 2776819
                      type: media
                      url: https://cdn.linqapp.com/attachments/example/photo.gif
                  read_at: null
                  sent_at: '2026-02-05T19:52:17.219Z'
                  updated_at: '2026-02-05T19:52:18.084038Z'
                preferred_service: null
                received_at: null
                recipient_handle: null
                recipient_phone: null
                service: iMessage
      responses:
        '200':
          description: Webhook received successfully
  message.received.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookMessageReceivedV2025
      summary: Message received
      description: |
        Triggered when an incoming message is received on your phone number.
        Contains the full message content including any attachments.

        **Timestamps:** `sent_at` is set, `delivered_at` and `read_at` are null.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageReceivedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: message.received
              event_id: 2915e81c-5068-4796-ace2-21d2c94ad298
              created_at: '2026-02-05T19:31:13.736444093Z'
              trace_id: 8af9171a45022df2eb74ba4e4c83be0f
              partner_id: your-partner-id
              data:
                chat_id: 8f392755-6865-4b18-880a-227f9d8b458f
                from: '+12025559876'
                from_handle:
                  handle: '+12025559876'
                  id: e604375a-5913-483a-8278-c631e8f0ffda
                  is_me: false
                  joined_at: '2026-01-04T05:48:51.321469Z'
                  left_at: null
                  service: iMessage
                  status: active
                idempotency_key: null
                is_from_me: false
                is_group: false
                message:
                  created_at: '2026-02-05T19:31:12.892Z'
                  delivered_at: null
                  id: 89e3566e-1d13-49e5-a8ee-48490d5bfeb7
                  is_delivered: false
                  is_read: false
                  parts:
                    - type: text
                      value: Hello!
                  read_at: null
                  sent_at: '2026-02-05T19:31:13.074Z'
                  updated_at: '2026-02-05T19:31:13.712Z'
                preferred_service: null
                received_at: '2026-02-05T19:31:13.074Z'
                recipient_handle:
                  handle: '+12025551234'
                  id: 6d6c617f-187a-4dcd-a0d5-988347a8c092
                  is_me: true
                  joined_at: '2026-01-04T05:48:51.321469Z'
                  left_at: null
                  service: iMessage
                  status: active
                recipient_phone: null
                service: iMessage
      responses:
        '200':
          description: Webhook received successfully
  message.read.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookMessageReadV2025
      summary: Message read
      description: |
        Triggered when a sent message has been read by the recipient.
        Only available for iMessage conversations with read receipts enabled.

        **Timestamps:** `sent_at`, `delivered_at`, and `read_at` are all set.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageReadWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: message.read
              event_id: 8fd42065-b998-482a-93b3-da855f8dad17
              created_at: '2026-02-05T19:13:58.833366566Z'
              trace_id: cbb93c08fa1a3f3c4c2efc161d67f36d
              partner_id: your-partner-id
              data:
                chat_id: 24e33345-e6cf-4f50-9d35-1d7fde8c9818
                from: '+12025551234'
                from_handle:
                  handle: '+12025551234'
                  id: d31678e9-0442-48fd-b7ed-c898d245dd15
                  is_me: true
                  joined_at: '2026-01-18T03:38:41.442254Z'
                  left_at: null
                  service: iMessage
                  status: active
                idempotency_key: null
                is_from_me: true
                is_group: false
                message:
                  created_at: '2026-02-05T19:13:57.612Z'
                  delivered_at: '2026-02-05T19:13:57.948Z'
                  id: dc6d3f68-90df-48f0-a504-e65f239a383c
                  is_delivered: true
                  is_read: true
                  parts:
                    - type: text
                      value: Hello world!
                  read_at: '2026-02-05T19:13:58.177Z'
                  sent_at: '2026-02-05T19:13:57.814Z'
                  updated_at: '2026-02-05T19:13:58.811Z'
                message_id: dc6d3f68-90df-48f0-a504-e65f239a383c
                preferred_service: null
                read_at: '2026-02-05T19:13:58.177Z'
                received_at: null
                recipient_handle: null
                recipient_phone: null
                service: iMessage
      responses:
        '200':
          description: Webhook received successfully
  message.delivered.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookMessageDeliveredV2025
      summary: Message delivered
      description: |
        Triggered when a sent message has been delivered to the recipient's device.
        This confirms the message reached the recipient, but does not indicate it was read.
        Only available for iMessage conversations with delivery receipts.

        **Timestamps:** `sent_at` and `delivered_at` are set, `read_at` is null.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageDeliveredWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: message.delivered
              event_id: 67c4ad39-e9b0-47f6-82f8-64bdd8ceafa6
              created_at: '2026-02-05T19:52:22.593689073Z'
              trace_id: abde7f6248fba00f97e8c7dc4782d7e0
              partner_id: your-partner-id
              data:
                chat_id: 0c961e93-e7bf-4db2-bf7b-ea06826bcab4
                from: '+12025551234'
                from_handle:
                  handle: '+12025551234'
                  id: 8d79532a-f529-4244-a5cf-d443de051434
                  is_me: true
                  joined_at: '2026-01-21T21:59:45.191571Z'
                  left_at: null
                  service: iMessage
                  status: active
                idempotency_key: null
                is_from_me: true
                is_group: false
                message:
                  created_at: '2026-02-05T19:52:17.041183Z'
                  delivered_at: '2026-02-05T19:52:22.291Z'
                  id: 347d62c2-2170-4754-8d30-c76d0c727d96
                  is_delivered: true
                  is_read: false
                  parts:
                    - type: text
                      value: Hello from Linq!
                    - filename: photo.gif
                      id: f13dda7d-ecac-49eb-b3fe-16fe286abf19
                      mime_type: image/gif
                      size_bytes: 2776819
                      type: media
                      url: https://cdn.linqapp.com/attachments/example/photo.gif
                  read_at: null
                  sent_at: '2026-02-05T19:52:17.219Z'
                  updated_at: '2026-02-05T19:52:22.571Z'
                delivered_at: '2026-02-05T19:52:22.291Z'
                message_id: 347d62c2-2170-4754-8d30-c76d0c727d96
                preferred_service: null
                received_at: null
                recipient_handle: null
                recipient_phone: null
                service: iMessage
      responses:
        '200':
          description: Webhook received successfully
  message.failed.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookMessageFailedV2025
      summary: Message delivery failed
      description: |
        Triggered when a message fails to be delivered. This can happen due to:
        - Request timeout (message expired before being processed)
        - Upstream service error (processing failed after retries)
        - Request cancelled (message was explicitly cancelled)
        - Service unavailable (no service available to process the request)

        Note: The original message content may not be available if the message
        expired before being consumed by a processor.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageFailedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: message.failed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                message_id: 550e8400-e29b-41d4-a716-446655440001
                code: 4001
                reason: Delivery failed
                failed_at: '2025-11-23T17:35:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  reaction.added.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookReactionAddedV2025
      summary: Reaction added
      description: |
        Triggered when a reaction (tapback) is added to a message.
        Includes standard reactions like "loved", "liked", "disliked", etc.
        and custom emoji reactions.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReactionAddedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: reaction.added
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                message_id: 550e8400-e29b-41d4-a716-446655440001
                part_index: 0
                reaction_type: love
                custom_emoji: null
                is_from_me: false
                from: '+14155559876'
                from_handle:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: null
                service: iMessage
                reacted_at: '2025-11-23T17:35:00.000Z'
                sticker: null
      responses:
        '200':
          description: Webhook received successfully
  reaction.removed.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookReactionRemovedV2025
      summary: Reaction removed
      description: |
        Triggered when a reaction (tapback) is removed from a message.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReactionRemovedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: reaction.removed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                message_id: 550e8400-e29b-41d4-a716-446655440001
                part_index: 0
                reaction_type: love
                custom_emoji: null
                is_from_me: false
                from: '+14155559876'
                from_handle:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: null
                service: iMessage
                reacted_at: '2025-11-23T17:35:00.000Z'
                sticker: null
      responses:
        '200':
          description: Webhook received successfully
  participant.added.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookParticipantAddedV2025
      summary: Participant added
      description: |
        Triggered when a new participant is added to a group chat.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ParticipantAddedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: participant.added
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                handle: '+14155559876'
                participant:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:40:00.000Z'
                  left_at: null
                added_at: '2025-11-23T17:40:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  participant.removed.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookParticipantRemovedV2025
      summary: Participant removed
      description: |
        Triggered when a participant is removed from a group chat.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ParticipantRemovedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: participant.removed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                handle: '+14155559876'
                participant:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: removed
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: '2025-11-23T17:45:00.000Z'
                removed_at: '2025-11-23T17:45:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.group_name_updated.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookChatGroupNameUpdatedV2025
      summary: Group name updated
      description: |
        Triggered when a group chat's display name is updated.
        A null `new_value` indicates the name was removed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatGroupNameUpdatedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: chat.group_name_updated
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                old_value: Old Group Name
                new_value: New Group Name
                changed_by_handle:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: null
                updated_at: '2025-11-23T17:50:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.group_icon_updated.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookChatGroupIconUpdatedV2025
      summary: Group icon updated
      description: |
        Triggered when a group chat's icon is updated.
        A null `new_value` indicates the icon was removed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatGroupIconUpdatedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: chat.group_icon_updated
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                old_value: https://example.com/old-icon.png
                new_value: https://example.com/new-icon.png
                changed_by_handle:
                  id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  is_me: false
                  service: iMessage
                  status: active
                  joined_at: '2025-11-23T17:30:00.000Z'
                  left_at: null
                updated_at: '2025-11-23T17:50:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.group_name_update_failed.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookChatGroupNameUpdateFailedV2025
      summary: Group name update failed
      description: Triggered when a group chat name update request fails.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatGroupNameUpdateFailedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: chat.group_name_update_failed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                error_code: 3007
                failed_at: '2025-11-23T17:55:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.group_icon_update_failed.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookChatGroupIconUpdateFailedV2025
      summary: Group icon update failed
      description: Triggered when a group chat icon update request fails.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatGroupIconUpdateFailedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: chat.group_icon_update_failed
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
                error_code: 3007
                failed_at: '2025-11-23T17:55:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.created.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookChatCreatedV2025
      summary: Chat created
      description: Triggered when a new chat is created.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCreatedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: chat.created
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                id: 550e8400-e29b-41d4-a716-446655440000
                display_name: +14155551234, +14155559876
                service: iMessage
                handles:
                  - id: 550e8400-e29b-41d4-a716-446655440010
                    handle: '+14155551234'
                    is_me: true
                    service: iMessage
                    status: active
                    joined_at: '2025-11-23T17:30:00.000Z'
                    left_at: null
                  - id: 550e8400-e29b-41d4-a716-446655440011
                    handle: '+14155559876'
                    is_me: false
                    service: iMessage
                    status: active
                    joined_at: '2025-11-23T17:30:00.000Z'
                    left_at: null
                is_group: false
                created_at: '2025-11-23T17:30:00.000Z'
                updated_at: '2025-11-23T17:30:00.000Z'
      responses:
        '200':
          description: Webhook received successfully
  chat.typing_indicator.started.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookChatTypingIndicatorStartedV2025
      summary: Typing indicator started
      description: Triggered when a participant starts typing in a chat.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatTypingIndicatorStartedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: chat.typing_indicator.started
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
      responses:
        '200':
          description: Webhook received successfully
  chat.typing_indicator.stopped.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookChatTypingIndicatorStoppedV2025
      summary: Typing indicator stopped
      description: Triggered when a participant stops typing in a chat.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatTypingIndicatorStoppedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: chat.typing_indicator.stopped
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2025-11-23T17:35:00.000Z'
              trace_id: abc123def456
              partner_id: your-partner-id
              data:
                chat_id: 550e8400-e29b-41d4-a716-446655440000
      responses:
        '200':
          description: Webhook received successfully
  phone_number.status_updated.v2025:
    post:
      tags:
        - '2025-01-01'
      operationId: webhookPhoneNumberStatusUpdatedV2025
      summary: Phone number status updated
      description: |
        Triggered when a phone number's service status changes from active to flagged or vice versa.
        Use this to monitor phone number health and availability.

        **Status values:**
        - `ACTIVE` — Phone number is healthy and can send/receive messages
        - `FLAGGED` — Phone number has been flagged and may have limited functionality
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PhoneNumberStatusUpdatedWebhook'
            example:
              api_version: v3
              webhook_version: '2025-01-01'
              event_type: phone_number.status_updated
              event_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              created_at: '2026-02-18T18:35:05.363Z'
              trace_id: b66e67c5c6b2c20e41d53c51698db27a
              partner_id: your-partner-id
              data:
                phone_number: '+12025551234'
                previous_status: ACTIVE
                new_status: FLAGGED
                changed_at: '2026-02-18T18:35:05.000Z'
      responses:
        '200':
          description: Webhook received successfully
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        Bearer token authentication. Include your API token in the Authorization header.

        Format: `Authorization: Bearer <your-token>`
  responses:
    BadRequest:
      description: Invalid request - validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              status: 400
              code: 1002
              message: Phone number must be in E.164 format
            success: false
    Unauthorized:
      description: Unauthorized - missing or invalid authentication
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              status: 401
              code: 2004
              message: Unauthorized - missing or invalid authentication token
            success: false
    Forbidden:
      description: Forbidden - no access to this resource
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              status: 403
              code: 2005
              message: Access denied - insufficient permissions for this resource
            success: false
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              status: 404
              code: 2001
              message: Resource not found
            success: false
    UnprocessableEntity:
      description: Unprocessable Entity - request is valid but cannot be processed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              status: 422
              code: 1003
              message: Request is valid but cannot be processed
            success: false
    RateLimited:
      description: Too many requests - rate limit exceeded
      headers:
        Retry-After:
          description: Number of seconds until the rate limit resets
          schema:
            type: integer
          example: 10
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              status: 429
              code: 1007
              message: Rate limited. Try again in 45 seconds.
              retry_after: 45
            success: false
    Conflict:
      description: The request conflicts with the current state of the resource
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              status: 409
              code: 2013
              message: This chat is unavailable
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              status: 500
              code: 3006
              message: Internal server error
            success: false
    ServiceUnavailable:
      description: Service temporarily unavailable
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              status: 503
              code: 4004
              message: RCS capability check is temporarily unavailable.
            success: false
  schemas:
    ContactCardItem:
      type: object
      required:
        - phone_number
        - first_name
        - is_active
      properties:
        phone_number:
          type: string
          example: '+15551234567'
        first_name:
          type: string
          example: John
        last_name:
          type: string
          example: Doe
        image_url:
          type: string
          example: https://cdn.linqapp.com/contact-card/example.jpg
        is_active:
          type: boolean
          example: true
    GetContactCardResponse:
      type: object
      required:
        - contact_cards
      properties:
        contact_cards:
          type: array
          items:
            $ref: '#/components/schemas/ContactCardItem'
    SetContactCardRequest:
      type: object
      required:
        - phone_number
        - first_name
      properties:
        phone_number:
          type: string
          description: E.164 phone number to associate the contact card with
          example: '+15551234567'
        first_name:
          type: string
          description: First name for the contact card. Required.
          example: John
        last_name:
          type: string
          description: Last name for the contact card. Optional.
          example: Doe
        image_url:
          type: string
          description: URL of the profile image to rehost on the CDN. Only re-uploaded when a new value is provided.
          example: https://cdn.linqapp.com/contact-card/example.jpg
    UpdateContactCardRequest:
      type: object
      properties:
        first_name:
          type: string
          description: Updated first name. If omitted, the existing value is kept.
          example: John
        last_name:
          type: string
          description: Updated last name. If omitted, the existing value is kept.
          example: Doe
        image_url:
          type: string
          description: Updated profile image URL. If omitted, the existing image is kept.
          example: https://cdn.linqapp.com/contact-card/example.jpg
    SetContactCardResponse:
      type: object
      required:
        - phone_number
        - first_name
        - is_active
      properties:
        phone_number:
          type: string
          description: The phone number the contact card is associated with
          example: '+15551234567'
        first_name:
          type: string
          description: First name on the contact card
          example: John
        last_name:
          type: string
          description: Last name on the contact card
          example: Doe
        image_url:
          type: string
          description: Image URL on the contact card
          example: https://cdn.linqapp.com/contact-card/example.jpg
        is_active:
          type: boolean
          description: Whether the contact card was successfully applied to the device
          example: true
    HandleCheckRequest:
      type: object
      required:
        - address
      properties:
        address:
          type: string
          description: The recipient phone number or email address to check
          example: '+15551234567'
        from:
          type: string
          description: Optional sender phone number. If omitted, an available phone from your pool is used automatically.
          example: '+15559876543'
    HandleCheckResponse:
      type: object
      required:
        - address
        - available
      properties:
        address:
          type: string
          description: The recipient address that was checked
          example: '+15551234567'
        available:
          type: boolean
          description: Whether the recipient supports the checked messaging service
          example: true
    DeliveryStatus:
      type: string
      description: Current delivery status of a message
      enum:
        - pending
        - queued
        - sent
        - delivered
        - failed
      example: pending
    ServiceType:
      type: string
      description: Messaging service type
      enum:
        - iMessage
        - SMS
        - RCS
      example: iMessage
    ServicePreferenceType:
      type: string
      description: Preferred messaging service type. Includes "auto" for default fallback behavior.
      enum:
        - iMessage
        - SMS
        - RCS
        - auto
      example: iMessage
    SentMessage:
      type: object
      description: A message that was sent (used in CreateChat and SendMessage responses)
      required:
        - id
        - parts
        - sent_at
        - delivery_status
        - is_read
      properties:
        id:
          type: string
          format: uuid
          description: Message identifier (UUID)
          example: 69a37c7d-af4f-4b5e-af42-e28e98ce873a
        service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          nullable: true
          description: Service used to send this message
          example: iMessage
        preferred_service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          nullable: true
          description: Preferred service for sending this message
          example: iMessage
        parts:
          type: array
          description: Message parts in order (text, media, and link)
          items:
            oneOf:
              - $ref: '#/components/schemas/TextPartResponse'
              - $ref: '#/components/schemas/MediaPartResponse'
              - $ref: '#/components/schemas/LinkPartResponse'
        sent_at:
          type: string
          format: date-time
          description: When the message was sent
          example: '2025-10-23T13:07:55.019-05:00'
        delivered_at:
          type: string
          format: date-time
          description: When the message was delivered
          example: null
          nullable: true
        delivery_status:
          $ref: '#/components/schemas/DeliveryStatus'
        is_read:
          type: boolean
          description: Whether the message has been read
          example: false
        effect:
          allOf:
            - $ref: '#/components/schemas/MessageEffect'
          nullable: true
          description: iMessage effect applied to this message (screen or bubble effect)
        from_handle:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: The sender of this message as a full handle object
        reply_to:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/ReplyTo'
    SendMessageResponse:
      type: object
      description: Response for sending a message to a chat
      required:
        - chat_id
        - message
      properties:
        chat_id:
          type: string
          format: uuid
          description: Unique identifier of the chat this message was sent to
          example: 550e8400-e29b-41d4-a716-446655440000
        message:
          $ref: '#/components/schemas/SentMessage'
    GetThreadResponse:
      type: object
      description: Response containing messages in a thread with pagination
      required:
        - messages
      properties:
        messages:
          type: array
          description: Messages in the thread, ordered by the specified order parameter
          items:
            $ref: '#/components/schemas/Message'
        next_cursor:
          type: string
          description: Cursor for fetching the next page of results (null if no more results)
          nullable: true
          example: eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0cyI6MTYzMDUwMDAwMH0=
    SendVoiceMemoToChatRequest:
      type: object
      description: Request to send a voice memo to a chat (chat_id provided in path)
      required:
        - voice_memo_url
      properties:
        voice_memo_url:
          type: string
          format: uri
          description: URL of the voice memo audio file. Must be a publicly accessible HTTPS URL.
          example: https://example.com/voice-memo.m4a
    SendVoiceMemoResponse:
      type: object
      required:
        - success
        - message
        - chat
      properties:
        success:
          type: boolean
          description: Whether the voice memo was successfully queued
          example: true
        message:
          $ref: '#/components/schemas/VoiceMemoMessage'
        chat:
          $ref: '#/components/schemas/ChatInfo'
    SendVoiceMemoToChatResult:
      type: object
      description: Response for sending a voice memo to a chat
      required:
        - voice_memo
      properties:
        voice_memo:
          type: object
          required:
            - id
            - from
            - to
            - status
            - voice_memo
            - created_at
            - chat
          properties:
            id:
              type: string
              format: uuid
              description: Message identifier
              example: 69a37c7d-af4f-4b5e-af42-e28e98ce873a
            from:
              type: string
              description: Sender phone number
              example: '+12052535597'
            to:
              type: array
              items:
                type: string
              description: Recipient handles (phone numbers or email addresses)
              example:
                - '+12052532136'
            status:
              type: string
              description: Current delivery status
              example: queued
            service:
              allOf:
                - $ref: '#/components/schemas/ServiceType'
              nullable: true
              description: Service used to send this voice memo
              example: iMessage
            voice_memo:
              $ref: '#/components/schemas/VoiceMemoAttachment'
            created_at:
              type: string
              format: date-time
              description: When the voice memo was created
            chat:
              $ref: '#/components/schemas/ChatInfo'
    VoiceMemoMessage:
      type: object
      required:
        - id
        - chat_id
        - from
        - to
        - voice_memo
        - status
        - created_at
      properties:
        id:
          type: string
          format: uuid
          description: Message identifier
          example: 69a37c7d-af4f-4b5e-af42-e28e98ce873a
        chat_id:
          type: string
          format: uuid
          description: Chat identifier
          example: 94c6bf33-31d9-40e3-a0e9-f94250ecedb9
        from:
          type: string
          description: 'DEPRECATED: Use from_handle instead. Sender phone number.'
          deprecated: true
          example: '+12052535597'
        from_handle:
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: The sender of this voice memo as a full handle object
        to:
          type: array
          items:
            type: string
          description: Recipient handles (phone numbers or email addresses)
          example:
            - '+12052532136'
        voice_memo:
          $ref: '#/components/schemas/VoiceMemoAttachment'
        status:
          type: string
          description: Current delivery status
          example: queued
        created_at:
          type: string
          format: date-time
          description: When the voice memo was created
    VoiceMemoAttachment:
      type: object
      required:
        - id
        - url
        - filename
        - mime_type
        - size_bytes
      properties:
        id:
          type: string
          format: uuid
          description: Attachment identifier
          example: 550e8400-e29b-41d4-a716-446655440000
        url:
          type: string
          format: uri
          description: CDN URL for downloading the voice memo
          example: https://cdn.linqapp.com/voice-memos/abc123.m4a
        filename:
          type: string
          description: Original filename
          example: voice-memo.m4a
        mime_type:
          type: string
          description: Audio MIME type
          example: audio/x-m4a
        size_bytes:
          type: integer
          description: File size in bytes
          example: 524288
        duration_ms:
          type: integer
          description: Duration in milliseconds
          example: 15000
          nullable: true
    ChatInfo:
      type: object
      required:
        - id
        - handles
        - is_group
        - service
        - is_active
      properties:
        id:
          type: string
          format: uuid
          description: Chat identifier
        handles:
          type: array
          description: Chat participants
          items:
            $ref: '#/components/schemas/ChatHandle'
        is_group:
          type: boolean
          description: Whether this is a group chat
        service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          description: Messaging service
          example: iMessage
        is_active:
          type: boolean
          description: Whether the chat is active
    MessagePart:
      type: object
      required:
        - type
      discriminator:
        propertyName: type
        mapping:
          text: '#/components/schemas/TextPart'
          media: '#/components/schemas/MediaPart'
          link: '#/components/schemas/LinkPart'
      oneOf:
        - $ref: '#/components/schemas/TextPart'
        - $ref: '#/components/schemas/MediaPart'
        - $ref: '#/components/schemas/LinkPart'
    TextPart:
      type: object
      required:
        - type
        - value
      properties:
        type:
          type: string
          enum:
            - text
          description: Indicates this is a text message part
        value:
          type: string
          description: |
            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).
          minLength: 1
          maxLength: 10000
          example: Hello world
        text_decorations:
          type: array
          maxItems: 100
          description: |
            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:** Text decorations only render for iMessage recipients. For SMS/RCS, text decorations are not applied.
          items:
            $ref: '#/components/schemas/TextDecoration'
          example:
            - range:
                - 0
                - 5
              style: bold
            - range:
                - 6
                - 11
              animation: shake
      example:
        type: text
        value: Hello world
        text_decorations:
          - range:
              - 0
              - 5
            style: bold
          - range:
              - 6
              - 11
            animation: shake
    TextDecoration:
      type: object
      required:
        - range
      properties:
        range:
          type: array
          items:
            type: integer
          minItems: 2
          maxItems: 2
          description: |
            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.*
          example:
            - 0
            - 5
        style:
          type: string
          enum:
            - bold
            - italic
            - strikethrough
            - underline
          description: Text style to apply. Mutually exclusive with `animation`.
          example: bold
        animation:
          type: string
          enum:
            - big
            - small
            - shake
            - nod
            - explode
            - ripple
            - bloom
            - jitter
          description: Animated text effect to apply. Mutually exclusive with `style`.
          example: shake
    MediaPart:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - media
          description: Indicates this is a media attachment part
        url:
          type: string
          format: uri
          description: |
            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.
          pattern: ^https://
          example: https://example.com/images/photo.jpg
        attachment_id:
          type: string
          format: uuid
          description: |
            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.
          example: 550e8400-e29b-41d4-a716-446655440000
      example:
        type: media
        url: https://cdn.linqapp.com/attachments/example/photo.jpg
    LinkPart:
      type: object
      required:
        - type
        - value
      properties:
        type:
          type: string
          enum:
            - link
          description: Indicates this is a rich link preview part
        value:
          type: string
          format: uri
          description: |
            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.
          minLength: 1
          maxLength: 2048
          example: https://github.com
      example:
        type: link
        value: https://github.com
    LinkPartResponse:
      type: object
      description: A rich link preview part
      required:
        - type
        - value
        - reactions
      properties:
        type:
          type: string
          enum:
            - link
          description: Indicates this is a rich link preview part
        value:
          type: string
          description: The URL
          example: https://github.com
        reactions:
          type: array
          nullable: true
          description: Reactions on this message part
          items:
            $ref: '#/components/schemas/Reaction'
      example:
        type: link
        value: https://github.com
    TextPartResponse:
      type: object
      description: A text message part
      required:
        - type
        - value
        - reactions
      properties:
        type:
          type: string
          enum:
            - text
          description: Indicates this is a text message part
        value:
          type: string
          description: The text content
          example: Check this out!
        text_decorations:
          type: array
          nullable: true
          description: Text decorations applied to character ranges in the value
          items:
            $ref: '#/components/schemas/TextDecoration'
        reactions:
          type: array
          nullable: true
          description: Reactions on this message part
          items:
            $ref: '#/components/schemas/Reaction'
      example:
        type: text
        value: Hello!
    MediaPartResponse:
      type: object
      description: A media attachment part
      required:
        - type
        - id
        - url
        - filename
        - mime_type
        - size_bytes
        - reactions
      properties:
        type:
          type: string
          enum:
            - media
          description: Indicates this is a media attachment part
        id:
          type: string
          format: uuid
          description: Unique attachment identifier
          example: abc12345-1234-5678-9abc-def012345678
        url:
          type: string
          format: uri
          description: |
            Presigned URL for downloading the attachment (expires in 1 hour).
          example: https://cdn.linqapp.com/attachments/550e8400/photo.jpg?signature=...
        filename:
          type: string
          description: Original filename
          example: photo.jpg
        mime_type:
          type: string
          description: MIME type of the file
          example: image/jpeg
        size_bytes:
          type: integer
          description: File size in bytes
          example: 245678
        reactions:
          type: array
          nullable: true
          description: Reactions on this message part
          items:
            $ref: '#/components/schemas/Reaction'
      example:
        type: media
        id: abc12345-1234-5678-9abc-def012345678
        url: https://cdn.linqapp.com/attachments/abc12345/photo.jpg?signature=...
        filename: photo.jpg
        mime_type: image/jpeg
        size_bytes: 245678
    MessageContent:
      type: object
      description: |
        Message content container. Groups all message-related fields together,
        separating the "what" (message content) from the "where" (routing fields like from/to).
      required:
        - parts
      properties:
        parts:
          type: array
          description: |
            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

            **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.
            - 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`.
          minItems: 1
          maxItems: 100
          items:
            $ref: '#/components/schemas/MessagePart'
          example:
            - type: text
              value: Check this out!
            - type: media
              url: https://cdn.linqapp.com/attachments/example/photo.jpg
        effect:
          $ref: '#/components/schemas/MessageEffect'
          description: iMessage effect to apply to this message (screen or bubble effect)
        reply_to:
          $ref: '#/components/schemas/ReplyTo'
          description: Reply to another message to create a threaded conversation
        idempotency_key:
          type: string
          description: |
            Optional idempotency key for this message.
            Use this to prevent duplicate sends of the same message.
          maxLength: 255
          example: msg-abc123xyz
        preferred_service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          description: |
            Preferred messaging service to use for this message.
            If not specified, uses default fallback chain: iMessage → RCS → SMS.
            - iMessage: Enforces iMessage without fallback to RCS or SMS. Message fails if recipient doesn't support iMessage.
            - RCS: Enforces RCS or SMS (no iMessage). Uses RCS if recipient supports it, otherwise falls back to SMS.
            - SMS: Enforces SMS (no iMessage). Uses RCS if recipient supports it, otherwise falls back to SMS.
          example: iMessage
    ErrorResponse:
      type: object
      required:
        - error
        - success
      properties:
        error:
          $ref: '#/components/schemas/ErrorDetail'
        success:
          type: boolean
          description: Always false for error responses
          example: false
        trace_id:
          type: string
          description: Unique trace ID for request tracing and debugging
          example: trace_abc123def456
    ErrorDetail:
      type: object
      required:
        - status
        - code
        - message
      properties:
        status:
          type: integer
          description: HTTP status code (e.g., 400, 404, 500)
          example: 400
        code:
          $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
          description: Human-readable error message
          example: Phone number must be in E.164 format
        retry_after:
          type: integer
          description: Number of seconds to wait before retrying. Only present on 429 rate limit errors.
          example: 60
    Message:
      type: object
      required:
        - id
        - chat_id
        - is_from_me
        - is_delivered
        - is_read
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: Unique identifier for the message
          format: uuid
          example: 69a37c7d-af4f-4b5e-af42-e28e98ce873a
        chat_id:
          type: string
          description: ID of the chat this message belongs to
          format: uuid
          example: 94c6bf33-31d9-40e3-a0e9-f94250ecedb9
        service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          nullable: true
          description: Service used to send/receive this message
          example: iMessage
        preferred_service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          nullable: true
          description: Preferred service for sending this message
          example: iMessage
        from:
          type: string
          nullable: true
          description: 'DEPRECATED: Use from_handle instead. Phone number of the message sender.'
          deprecated: true
          example: '+12052535597'
        from_handle:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: The sender of this message as a full handle object
        parts:
          type: array
          nullable: true
          description: Message parts in order (text, media, and link)
          items:
            oneOf:
              - $ref: '#/components/schemas/TextPartResponse'
              - $ref: '#/components/schemas/MediaPartResponse'
              - $ref: '#/components/schemas/LinkPartResponse'
        reply_to:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/ReplyTo'
        is_from_me:
          type: boolean
          description: Whether this message was sent by the authenticated user
          example: true
        is_delivered:
          type: boolean
          description: Whether the message has been delivered
          example: true
        is_read:
          type: boolean
          description: Whether the message has been read
          example: false
        created_at:
          type: string
          format: date-time
          description: When the message was created
          example: '2024-01-15T10:30:00Z'
        updated_at:
          type: string
          format: date-time
          description: When the message was last updated
          example: '2024-01-15T10:30:00Z'
        sent_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was sent
          example: '2024-01-15T10:30:05Z'
        delivered_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was delivered
          example: '2024-01-15T10:30:10Z'
        read_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was read
          example: '2024-01-15T10:35:00Z'
        effect:
          allOf:
            - $ref: '#/components/schemas/MessageEffect'
          nullable: true
          description: iMessage effect applied to this message (screen or bubble effect)
    MessageEffect:
      type: object
      description: iMessage effect applied to a message (screen or bubble effect)
      properties:
        type:
          type: string
          description: Type of effect
          enum:
            - screen
            - bubble
          example: screen
        name:
          type: string
          description: |
            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
          example: confetti
    ReplyTo:
      type: object
      description: Indicates this message is a threaded reply to another message
      required:
        - message_id
      properties:
        message_id:
          type: string
          format: uuid
          description: The ID of the message to reply to
          example: 550e8400-e29b-41d4-a716-446655440000
        part_index:
          type: integer
          format: int32
          description: |
            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.
          minimum: 0
          example: 0
    Attachment:
      type: object
      required:
        - id
        - filename
        - content_type
        - size_bytes
        - status
        - created_at
      properties:
        id:
          type: string
          description: Unique identifier for the attachment (UUID)
          example: 550e8400-e29b-41d4-a716-446655440000
        filename:
          type: string
          description: Original filename of the attachment
          example: photo.jpg
        content_type:
          $ref: '#/components/schemas/SupportedContentType'
        size_bytes:
          type: integer
          format: int64
          description: Size of the attachment in bytes
          example: 1024000
        status:
          type: string
          description: Current upload/processing status
          enum:
            - pending
            - complete
            - failed
          example: complete
        download_url:
          type: string
          format: uri
          description: URL to download the attachment
        created_at:
          type: string
          format: date-time
          description: When the attachment was created
          example: '2024-01-15T10:30:00Z'
    ReactionType:
      type: string
      description: |
        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.
      enum:
        - love
        - like
        - dislike
        - laugh
        - emphasize
        - question
        - custom
        - sticker
      example: love
    Reaction:
      type: object
      required:
        - is_me
        - handle
        - type
      properties:
        is_me:
          type: boolean
          description: Whether this reaction is from the current user
          example: false
        handle:
          $ref: '#/components/schemas/ChatHandle'
        type:
          $ref: '#/components/schemas/ReactionType'
        custom_emoji:
          type: string
          description: Custom emoji if type is "custom", null otherwise
          example: 🚀
          nullable: true
        sticker:
          type: object
          nullable: true
          description: Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.
          properties:
            url:
              type: string
              format: uri
              description: |
                Presigned URL for downloading the sticker image (expires in 1 hour).
              example: https://cdn.linqapp.com/attachments/a1b2c3d4/sticker.png?signature=...
            mime_type:
              type: string
              description: MIME type of the sticker image
              example: image/png
            width:
              type: integer
              description: Sticker image width in pixels
              example: 420
            height:
              type: integer
              description: Sticker image height in pixels
              example: 420
            file_name:
              type: string
              description: Filename of the sticker
              example: sticker.png
    EditMessageRequest:
      type: object
      required:
        - text
      properties:
        part_index:
          type: integer
          description: Index of the message part to edit. Defaults to 0.
          default: 0
          example: 0
        text:
          type: string
          description: New text content for the message part
          minLength: 1
          maxLength: 10000
          example: This is the edited message content
    SendReactionRequest:
      type: object
      required:
        - operation
        - type
      properties:
        operation:
          type: string
          description: Whether to add or remove the reaction
          enum:
            - add
            - remove
          example: add
        type:
          $ref: '#/components/schemas/ReactionType'
        custom_emoji:
          type: string
          description: |
            Custom emoji string. Required when type is "custom".
          example: 😍
        part_index:
          type: integer
          description: |
            Optional index of the message part to react to.
            If not provided, reacts to the entire message (part 0).
          example: 1
    SupportedContentType:
      type: string
      description: |
        Supported MIME types for file attachments and media URLs.

        **Images:** image/jpeg, image/png, image/gif, image/heic, image/heif, image/tiff, image/bmp

        **Videos:** video/mp4, video/quicktime, video/mpeg, video/3gpp

        **Audio:** audio/mpeg, audio/mp4, audio/x-m4a, audio/x-caf, audio/wav, audio/aiff, audio/aac, audio/amr

        **Documents:** application/pdf, text/plain, text/markdown, text/vcard, text/rtf, text/csv, text/html, text/calendar, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation, application/vnd.apple.pages, application/vnd.apple.numbers, application/vnd.apple.keynote, application/epub+zip, application/zip

        **Unsupported:** WebP, SVG, FLAC, OGG, and executable files are explicitly rejected.
      enum:
        - image/jpeg
        - image/jpg
        - image/png
        - image/gif
        - image/heic
        - image/heif
        - image/tiff
        - image/bmp
        - image/x-ms-bmp
        - video/mp4
        - video/quicktime
        - video/mpeg
        - video/x-m4v
        - video/3gpp
        - audio/mpeg
        - audio/mp3
        - audio/mp4
        - audio/x-m4a
        - audio/m4a
        - audio/x-caf
        - audio/wav
        - audio/x-wav
        - audio/aiff
        - audio/x-aiff
        - audio/aac
        - audio/x-aac
        - audio/amr
        - application/pdf
        - text/plain
        - text/markdown
        - text/vcard
        - text/x-vcard
        - text/rtf
        - application/rtf
        - text/csv
        - text/html
        - text/calendar
        - application/msword
        - application/vnd.openxmlformats-officedocument.wordprocessingml.document
        - application/vnd.ms-excel
        - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
        - application/vnd.ms-powerpoint
        - application/vnd.openxmlformats-officedocument.presentationml.presentation
        - application/vnd.apple.pages
        - application/x-iwork-pages-sffpages
        - application/vnd.apple.numbers
        - application/x-iwork-numbers-sffnumbers
        - application/vnd.apple.keynote
        - application/x-iwork-keynote-sffkey
        - application/epub+zip
        - application/zip
        - application/x-zip-compressed
      example: image/jpeg
    RequestUploadRequest:
      type: object
      required:
        - filename
        - content_type
        - size_bytes
      properties:
        filename:
          type: string
          description: Name of the file to upload
          minLength: 1
          maxLength: 255
          example: photo.jpg
        content_type:
          $ref: '#/components/schemas/SupportedContentType'
        size_bytes:
          type: integer
          format: int64
          description: Size of the file in bytes (max 100MB)
          minimum: 1
          maximum: 104857600
          example: 1024000
    RequestUploadResult:
      type: object
      required:
        - attachment_id
        - upload_url
        - download_url
        - http_method
        - expires_at
        - required_headers
      properties:
        attachment_id:
          type: string
          description: Unique identifier for the attachment (for status checks via GET /v3/attachments/{id})
          format: uuid
          example: abc12345-1234-5678-9abc-def012345678
        upload_url:
          type: string
          format: uri
          description: |
            Presigned URL for uploading the file. PUT the raw binary file content to this URL
            with the `required_headers`. Do not JSON-encode or multipart-wrap the body.
            Expires after 15 minutes.
          example: https://uploads.linqapp.com/attachments/abc12345?X-Amz-Algorithm=...
        download_url:
          type: string
          format: uri
          description: |
            Permanent CDN URL for the file. Does not expire. Use the `attachment_id`
            to reference this file in media parts when sending messages.
          example: https://cdn.linqapp.com/uploads/partner-id/abc12345/photo.jpg
        http_method:
          type: string
          description: HTTP method to use for upload (always PUT)
          enum:
            - PUT
          example: PUT
        expires_at:
          type: string
          format: date-time
          description: When the upload URL expires (15 minutes from now)
          example: '2024-01-15T10:45:00Z'
        required_headers:
          type: object
          description: |
            HTTP headers that must be set on the upload request. The presigned URL is signed
            with these exact values — S3 will reject the upload if they don't match.
          additionalProperties:
            type: string
          example:
            Content-Type: image/jpeg
            Content-Length: '1024000'
    Chat:
      type: object
      required:
        - id
        - display_name
        - handles
        - is_archived
        - is_group
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: Unique identifier for the chat
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        display_name:
          type: string
          description: Display name for the chat. Defaults to a comma-separated list of recipient handles. Can be updated for group chats.
          example: +14155551234, +14155559876
          nullable: true
        service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          description: Service type for the chat
          example: iMessage
          nullable: true
        handles:
          type: array
          description: List of chat participants with full handle details. Always contains at least two handles (your phone number and the other participant).
          items:
            $ref: '#/components/schemas/ChatHandle'
          example:
            - id: 550e8400-e29b-41d4-a716-446655440010
              handle: '+14155551234'
              service: iMessage
              status: active
              joined_at: '2025-05-21T15:30:00.000Z'
              is_me: true
            - id: 550e8400-e29b-41d4-a716-446655440011
              handle: '+14155559876'
              service: iMessage
              status: active
              joined_at: '2025-05-21T15:30:00.000Z'
              is_me: false
        is_archived:
          type: boolean
          description: Whether the chat is archived
          default: false
        is_group:
          type: boolean
          description: Whether this is a group chat
          default: false
        created_at:
          type: string
          format: date-time
          description: When the chat was created
          example: '2024-01-15T10:30:00Z'
        updated_at:
          type: string
          format: date-time
          description: When the chat was last updated
          example: '2024-01-15T10:30:00Z'
    ChatHandle:
      type: object
      required:
        - id
        - handle
        - service
        - joined_at
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this handle
          example: 550e8400-e29b-41d4-a716-446655440000
        handle:
          type: string
          description: Phone number (E.164) or email address of the participant
          example: '+15551234567'
        service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          description: Service type (iMessage, SMS, RCS, etc.)
          example: iMessage
        status:
          type: string
          nullable: true
          description: Participant status
          enum:
            - active
            - left
            - removed
          default: active
        joined_at:
          type: string
          format: date-time
          description: When this participant joined the chat
          example: '2025-05-21T15:30:00.000-05:00'
        left_at:
          type: string
          format: date-time
          description: When they left (if applicable)
          nullable: true
        is_me:
          type: boolean
          nullable: true
          description: Whether this handle belongs to the sender (your phone number)
          example: false
    ListChatsResult:
      type: object
      required:
        - chats
      properties:
        chats:
          type: array
          description: List of chats
          items:
            $ref: '#/components/schemas/Chat'
        next_cursor:
          type: string
          nullable: true
          description: |
            Cursor for fetching the next page of results.
            Null if there are no more results to fetch.
            Pass this value as the `cursor` parameter in the next request.
    CreateChatRequest:
      type: object
      required:
        - from
        - to
        - message
      properties:
        from:
          type: string
          description: |
            Sender phone number in E.164 format. Must be a phone number that the
            authenticated partner has permission to send from.
          pattern: ^\+[1-9][0-9]{1,14}$
          example: '+12052535597'
        to:
          type: array
          description: |
            Array of recipient handles (phone numbers in E.164 format or email addresses).
            For individual chats, provide one recipient. For group chats, provide multiple.
          minItems: 1
          maxItems: 31
          items:
            type: string
          example:
            - '+14155559876'
            - '+14155550123'
        message:
          $ref: '#/components/schemas/MessageContent'
    CreateChatResult:
      type: object
      description: Response for creating a new chat with an initial message
      required:
        - chat
      properties:
        chat:
          type: object
          required:
            - id
            - display_name
            - service
            - is_group
            - handles
            - message
          properties:
            id:
              type: string
              format: uuid
              description: Unique identifier for the created chat (UUID)
              example: 94c6bf33-31d9-40e3-a0e9-f94250ecedb9
            display_name:
              type: string
              description: Display name for the chat. Defaults to a comma-separated list of recipient handles. Can be updated for group chats.
              example: +14155551234, +14155559876
              nullable: true
            service:
              allOf:
                - $ref: '#/components/schemas/ServiceType'
              description: Messaging service used
              example: iMessage
            is_group:
              type: boolean
              description: Whether this is a group chat
              example: false
            handles:
              type: array
              description: List of participants in the chat. Always contains at least two handles (your phone number and the other participant).
              items:
                $ref: '#/components/schemas/ChatHandle'
              example:
                - id: 550e8400-e29b-41d4-a716-446655440010
                  handle: '+14155551234'
                  service: iMessage
                  status: active
                  joined_at: '2025-05-21T15:30:00.000Z'
                  is_me: true
                - id: 550e8400-e29b-41d4-a716-446655440011
                  handle: '+14155559876'
                  service: iMessage
                  status: active
                  joined_at: '2025-05-21T15:30:00.000Z'
                  is_me: false
            message:
              $ref: '#/components/schemas/SentMessage'
    UpdateChatRequest:
      type: object
      properties:
        display_name:
          type: string
          description: New display name for the chat (group chats only)
          example: Updated Team Name
        group_chat_icon:
          type: string
          format: uri
          description: URL of an image to set as the group chat icon (group chats only)
          example: https://example.com/icon.png
    AddParticipantRequest:
      type: object
      required:
        - handle
      properties:
        handle:
          type: string
          description: Phone number (E.164 format) or email address of the participant to add
          example: '+12052532136'
    RemoveParticipantRequest:
      type: object
      required:
        - handle
      properties:
        handle:
          type: string
          description: Phone number (E.164 format) or email address of the participant to remove
          example: '+12052532136'
    SendMessageToChatRequest:
      type: object
      required:
        - message
      properties:
        message:
          $ref: '#/components/schemas/MessageContent'
    GetMessagesResult:
      type: object
      required:
        - messages
      properties:
        messages:
          type: array
          description: List of messages
          items:
            $ref: '#/components/schemas/Message'
        next_cursor:
          type: string
          nullable: true
          description: |
            Cursor for fetching the next page of results.
            Null if there are no more results to fetch.
            Pass this value as the `cursor` parameter in the next request.
    WebhookEventType:
      type: string
      description: |
        Valid webhook event types that can be subscribed to.

        **Note:** `message.edited` is only delivered to subscriptions using `webhook_version: "2026-02-03"`.
        Subscribing to this event on a v2025 subscription will not produce any deliveries.
      enum:
        - message.sent
        - message.received
        - message.read
        - message.delivered
        - message.failed
        - message.edited
        - reaction.added
        - reaction.removed
        - participant.added
        - participant.removed
        - chat.created
        - chat.group_name_updated
        - chat.group_icon_updated
        - chat.group_name_update_failed
        - chat.group_icon_update_failed
        - chat.typing_indicator.started
        - chat.typing_indicator.stopped
        - phone_number.status_updated
    WebhookEventsResult:
      type: object
      required:
        - events
        - doc_url
      properties:
        events:
          type: array
          description: List of all available webhook event types
          items:
            $ref: '#/components/schemas/WebhookEventType'
        doc_url:
          type: string
          format: uri
          description: URL to the webhook events documentation
          const: https://apidocs.linqapp.com/documentation/webhook-events
    WebhookSubscriptionResponse:
      type: object
      required:
        - id
        - target_url
        - subscribed_events
        - is_active
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: Unique identifier for the webhook subscription
          example: b2c3d4e5-f6a7-8901-bcde-f23456789012
        target_url:
          type: string
          format: uri
          description: URL where webhook events will be sent
          example: https://webhooks.example.com/linq/events
        subscribed_events:
          type: array
          description: List of event types this subscription receives
          items:
            $ref: '#/components/schemas/WebhookEventType'
          example:
            - message.sent
            - message.delivered
            - message.read
        phone_numbers:
          type: array
          nullable: true
          description: Phone numbers this subscription filters for. If null or empty, events from all phone numbers are delivered.
          items:
            type: string
          example: null
        is_active:
          type: boolean
          description: Whether this subscription is currently active
          example: true
        created_at:
          type: string
          format: date-time
          description: When the subscription was created
          example: '2024-01-15T10:30:00Z'
        updated_at:
          type: string
          format: date-time
          description: When the subscription was last updated
          example: '2024-01-15T10:30:00Z'
    WebhookSubscriptionCreatedResponse:
      type: object
      description: Response returned when creating a webhook subscription. Includes the signing secret which is only shown once.
      required:
        - id
        - target_url
        - signing_secret
        - subscribed_events
        - is_active
        - created_at
        - updated_at
      properties:
        id:
          type: string
          description: Unique identifier for the webhook subscription
          example: b2c3d4e5-f6a7-8901-bcde-f23456789012
        target_url:
          type: string
          format: uri
          description: URL where webhook events will be sent
          example: https://webhooks.example.com/linq/events
        signing_secret:
          type: string
          description: Secret for verifying webhook signatures. Store this securely - it cannot be retrieved again.
          example: whsec_abc123def456
        subscribed_events:
          type: array
          description: List of event types this subscription receives
          items:
            $ref: '#/components/schemas/WebhookEventType'
          example:
            - message.sent
            - message.delivered
            - message.read
        phone_numbers:
          type: array
          nullable: true
          description: Phone numbers this subscription filters for. If null or empty, events from all phone numbers are delivered.
          items:
            type: string
          example: null
        is_active:
          type: boolean
          description: Whether this subscription is currently active
          example: true
        created_at:
          type: string
          format: date-time
          description: When the subscription was created
          example: '2024-01-15T10:30:00Z'
        updated_at:
          type: string
          format: date-time
          description: When the subscription was last updated
          example: '2024-01-15T10:30:00Z'
    CreateWebhookSubscriptionRequest:
      type: object
      required:
        - target_url
        - subscribed_events
      properties:
        target_url:
          type: string
          format: uri
          description: URL where webhook events will be sent. Must be HTTPS.
          example: https://webhooks.example.com/linq/events
        subscribed_events:
          type: array
          description: List of event types to subscribe to
          minItems: 1
          items:
            $ref: '#/components/schemas/WebhookEventType'
          example:
            - message.sent
            - message.delivered
        phone_numbers:
          type: array
          description: Optional list of phone numbers to filter events for. Only events originating from these phone numbers will be delivered to this subscription. If omitted or empty, events from all phone numbers are delivered. Phone numbers must be in E.164 format.
          items:
            type: string
          example:
            - '+12025551234'
            - '+12025559876'
    UpdateWebhookSubscriptionRequest:
      type: object
      properties:
        target_url:
          type: string
          format: uri
          description: New target URL for webhook events
          example: https://webhooks.example.com/linq/events
        subscribed_events:
          type: array
          description: Updated list of event types to subscribe to
          minItems: 1
          items:
            $ref: '#/components/schemas/WebhookEventType'
          example:
            - message.sent
            - message.delivered
        is_active:
          type: boolean
          description: Activate or deactivate the subscription
          example: true
        phone_numbers:
          type: array
          nullable: true
          description: Updated list of phone numbers to filter events for. Set to a non-empty array to filter events to specific phone numbers. Set to an empty array or null to remove the filter and receive events from all phone numbers. Phone numbers must be in E.164 format.
          items:
            type: string
          example:
            - '+12025551234'
    ListWebhookSubscriptionsResult:
      type: object
      required:
        - subscriptions
      properties:
        subscriptions:
          type: array
          description: List of webhook subscriptions
          items:
            $ref: '#/components/schemas/WebhookSubscriptionResponse'
    ListPhoneNumbersResult:
      type: object
      required:
        - phone_numbers
      properties:
        phone_numbers:
          type: array
          description: List of phone numbers assigned to the partner
          items:
            $ref: '#/components/schemas/PhoneNumberInfo'
    PhoneNumberInfo:
      type: object
      required:
        - id
        - phone_number
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the phone number
          example: 550e8400-e29b-41d4-a716-446655440000
        phone_number:
          type: string
          description: Phone number in E.164 format
          example: '+12025551234'
        type:
          type: string
          description: Deprecated. Always null.
          nullable: true
          example: null
        country_code:
          type: string
          description: Deprecated. Always null.
          example: US
        capabilities:
          $ref: '#/components/schemas/PhoneCapabilities'
    PhoneCapabilities:
      type: object
      required:
        - sms
        - mms
        - voice
      properties:
        sms:
          type: boolean
          description: Whether SMS messaging is supported
          example: true
        mms:
          type: boolean
          description: Whether MMS messaging is supported
          example: true
        voice:
          type: boolean
          description: Whether voice calls are supported
          example: false
    ListPhoneNumbersResultV2:
      type: object
      required:
        - phone_numbers
      properties:
        phone_numbers:
          type: array
          description: List of phone numbers assigned to the partner
          items:
            $ref: '#/components/schemas/PhoneNumberInfoV2'
    PhoneNumberInfoV2:
      type: object
      required:
        - id
        - phone_number
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the phone number
          example: 550e8400-e29b-41d4-a716-446655440000
        phone_number:
          type: string
          description: Phone number in E.164 format
          example: '+12025551234'
    ErrorCode:
      type: integer
      description: Linq API error code.
    WebhookEnvelopeBase:
      type: object
      required:
        - api_version
        - webhook_version
        - event_type
        - event_id
        - created_at
        - data
        - trace_id
        - partner_id
      properties:
        api_version:
          type: string
          description: API version for the webhook payload format
          example: v3
        webhook_version:
          type: string
          description: |
            Date-based webhook payload version.
            Determined by the `?version=` query parameter in your webhook subscription URL.
            If no version parameter is specified, defaults based on subscription creation date.
          example: '2025-01-01'
        event_type:
          $ref: '#/components/schemas/WebhookEventType'
        event_id:
          type: string
          format: uuid
          description: Unique identifier for this event (for deduplication)
          example: 550e8400-e29b-41d4-a716-446655440000
        created_at:
          type: string
          format: date-time
          description: When the event was created
          example: '2025-11-23T17:30:00Z'
        trace_id:
          type: string
          description: Trace ID for debugging and correlation across systems.
          example: abc123def456
        partner_id:
          type: string
          description: Partner identifier. Present on all webhooks for cross-referencing.
          example: partner_abc123
    schemas-TextPartResponse:
      type: object
      description: A text message part
      required:
        - type
        - value
      properties:
        type:
          type: string
          enum:
            - text
          description: Indicates this is a text message part
        value:
          type: string
          description: The text content
          example: Check this out!
        text_decorations:
          type: array
          nullable: true
          description: Text decorations applied to character ranges in the value
          items:
            $ref: '#/components/schemas/TextDecoration'
      example:
        type: text
        value: Hello!
    schemas-MediaPartResponse:
      type: object
      description: A media attachment part
      required:
        - type
        - id
        - url
        - filename
        - mime_type
        - size_bytes
      properties:
        type:
          type: string
          enum:
            - media
          description: Indicates this is a media attachment part
        id:
          type: string
          format: uuid
          description: Unique attachment identifier
          example: abc12345-1234-5678-9abc-def012345678
        url:
          type: string
          format: uri
          description: |
            Presigned URL for downloading the attachment (expires in 1 hour).
          example: https://cdn.linqapp.com/attachments/550e8400/photo.jpg?signature=...
        filename:
          type: string
          description: Original filename
          example: photo.jpg
        mime_type:
          type: string
          description: MIME type of the file
          example: image/jpeg
        size_bytes:
          type: integer
          description: File size in bytes
          example: 245678
      example:
        type: media
        id: abc12345-1234-5678-9abc-def012345678
        url: https://cdn.linqapp.com/attachments/abc12345/photo.jpg?signature=...
        filename: photo.jpg
        mime_type: image/jpeg
        size_bytes: 245678
    schemas-LinkPartResponse:
      type: object
      description: A rich link preview part
      required:
        - type
        - value
      properties:
        type:
          type: string
          enum:
            - link
          description: Indicates this is a rich link preview part
        value:
          type: string
          description: The URL
          example: https://github.com
      example:
        type: link
        value: https://github.com
    schemas-MessageEffect:
      type: object
      description: iMessage effect applied to a message (screen or bubble animation)
      properties:
        type:
          type: string
          description: Effect category
          enum:
            - screen
            - bubble
          example: bubble
        name:
          type: string
          description: Effect name (confetti, fireworks, slam, gentle, etc.)
          example: gentle
    MessageEventV2:
      type: object
      description: |
        Unified payload for message webhooks when using `webhook_version: "2026-02-03"`.

        This schema is used for message.sent, message.received, message.delivered, and message.read
        events when the subscription URL includes `?version=2026-02-03`.

        Key differences from V1 (2025-01-01):
        - `direction`: "inbound" or "outbound" instead of `is_from_me` boolean
        - `sender_handle`: Full handle object for the sender
        - `chat`: Nested object with `id`, `is_group`, and `owner_handle`
        - Message fields (`id`, `parts`, `effect`, etc.) are at the top level, not nested in `message`

        Timestamps indicate the message state:
        - `message.sent`: sent_at set, delivered_at=null, read_at=null
        - `message.received`: sent_at set, delivered_at=null, read_at=null
        - `message.delivered`: sent_at set, delivered_at set, read_at=null
        - `message.read`: sent_at set, delivered_at set, read_at set
      required:
        - chat
        - id
        - direction
        - sender_handle
        - service
        - parts
      properties:
        chat:
          type: object
          description: Chat information
          required:
            - id
          properties:
            id:
              type: string
              format: uuid
              description: Chat identifier
              example: 550e8400-e29b-41d4-a716-446655440000
            is_group:
              type: boolean
              nullable: true
              description: Whether this is a group chat
              example: true
            owner_handle:
              allOf:
                - $ref: '#/components/schemas/ChatHandle'
              nullable: true
              description: Your phone number's handle. Always has is_me=true.
        id:
          type: string
          format: uuid
          description: Message identifier
          example: 550e8400-e29b-41d4-a716-446655440001
        idempotency_key:
          type: string
          nullable: true
          description: Idempotency key for deduplication of outbound messages.
          example: unique-key
        direction:
          type: string
          description: Message direction - "outbound" if sent by you, "inbound" if received
          enum:
            - inbound
            - outbound
          example: outbound
        sender_handle:
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: The handle that sent this message
        parts:
          type: array
          description: Message parts (text and/or media)
          items:
            oneOf:
              - $ref: '#/components/schemas/schemas-TextPartResponse'
              - $ref: '#/components/schemas/schemas-MediaPartResponse'
              - $ref: '#/components/schemas/schemas-LinkPartResponse'
            discriminator:
              propertyName: type
              mapping:
                text: '#/components/schemas/schemas-TextPartResponse'
                media: '#/components/schemas/schemas-MediaPartResponse'
                link: '#/components/schemas/schemas-LinkPartResponse'
        sent_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was sent. Null if not yet sent.
          example: '2026-01-30T20:49:19.704Z'
        delivered_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was delivered. Null if not yet delivered.
          example: '2026-01-30T20:49:20.352Z'
        read_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was read. Null if not yet read.
          example: null
        reply_to:
          type: object
          nullable: true
          description: Reference to the message this is replying to (for threaded replies)
          properties:
            message_id:
              type: string
              format: uuid
              description: ID of the message being replied to
            part_index:
              type: integer
              format: int32
              minimum: 0
              description: Index of the part being replied to
        effect:
          allOf:
            - $ref: '#/components/schemas/schemas-MessageEffect'
          nullable: true
          description: iMessage effect applied to the message (bubble or screen animation). Null if no effect.
        service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          description: The service used to send/receive the message
          example: iMessage
        preferred_service:
          allOf:
            - $ref: '#/components/schemas/ServicePreferenceType'
          nullable: true
          description: The service that was requested when sending. Null for inbound messages.
          example: iMessage
    MessageSentWebhookV2:
      description: Complete webhook payload for message.sent events (2026-02-03 format)
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageEventV2'
    MessageReceivedWebhookV2:
      description: Complete webhook payload for message.received events (2026-02-03 format)
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageEventV2'
    MessageReadWebhookV2:
      description: Complete webhook payload for message.read events (2026-02-03 format)
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageEventV2'
    MessageDeliveredWebhookV2:
      description: Complete webhook payload for message.delivered events (2026-02-03 format)
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageEventV2'
    WebhookErrorCode:
      type: integer
      description: Error codes in webhook failure events (3007, 4001).
    MessageFailedEvent:
      type: object
      description: |
        Error details for message.failed webhook events.
        See [WebhookErrorCode](#/components/schemas/WebhookErrorCode) for the full error code reference.
      required:
        - code
        - failed_at
      properties:
        chat_id:
          type: string
          description: Chat identifier (UUID)
          example: 550e8400-e29b-41d4-a716-446655440000
        message_id:
          type: string
          description: Message identifier (UUID)
          example: 550e8400-e29b-41d4-a716-446655440001
        code:
          $ref: '#/components/schemas/WebhookErrorCode'
        reason:
          type: string
          description: Human-readable description of the failure
          example: Request expired before being processed
        failed_at:
          type: string
          format: date-time
          description: When the failure was detected
          example: '2025-11-23T17:35:00Z'
    MessageFailedWebhook:
      description: Complete webhook payload for message.failed events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageFailedEvent'
    ReactionEventBase:
      type: object
      required:
        - reaction_type
        - is_from_me
      properties:
        chat_id:
          type: string
          description: Chat identifier (UUID)
          example: 550e8400-e29b-41d4-a716-446655440000
        from:
          type: string
          description: 'DEPRECATED: Use from_handle instead. Phone number or email address of the person who added/removed the reaction.'
          example: '+14155559876'
          deprecated: true
        from_handle:
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: The person who added/removed the reaction as a full handle object
          example:
            id: 550e8400-e29b-41d4-a716-446655440011
            handle: '+14155559876'
            is_me: false
            service: iMessage
            status: active
            joined_at: '2025-11-23T17:30:00.000Z'
            left_at: null
        message_id:
          type: string
          description: Message identifier (UUID) that the reaction was added to or removed from
          example: 550e8400-e29b-41d4-a716-446655440001
        part_index:
          type: integer
          format: int32
          minimum: 0
          description: Index of the message part that was reacted to (0-based)
          example: 0
        reaction_type:
          $ref: '#/components/schemas/ReactionType'
        custom_emoji:
          type: string
          nullable: true
          description: The actual emoji when reaction_type is "custom". Null for standard tapbacks.
          example: null
        is_from_me:
          type: boolean
          description: Whether this reaction was from the owner of the phone number (true) or from someone else (false)
          example: false
        service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          description: Message service type
          example: iMessage
        reacted_at:
          type: string
          format: date-time
          description: When the reaction was added or removed
          example: '2025-11-23T17:35:00Z'
        sticker:
          type: object
          nullable: true
          description: Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.
          properties:
            url:
              type: string
              format: uri
              description: |
                Presigned URL for downloading the sticker image (expires in 1 hour).
              example: https://cdn.linqapp.com/attachments/a1b2c3d4/sticker.png?signature=...
            mime_type:
              type: string
              description: MIME type of the sticker image
              example: image/png
            width:
              type: integer
              description: Sticker image width in pixels
              example: 420
            height:
              type: integer
              description: Sticker image height in pixels
              example: 420
            file_name:
              type: string
              description: Filename of the sticker
              example: sticker.png
    ReactionAddedEvent:
      description: Payload for reaction.added webhook events
      allOf:
        - $ref: '#/components/schemas/ReactionEventBase'
    ReactionAddedWebhook:
      description: Complete webhook payload for reaction.added events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ReactionAddedEvent'
    ReactionRemovedEvent:
      description: Payload for reaction.removed webhook events
      allOf:
        - $ref: '#/components/schemas/ReactionEventBase'
    ReactionRemovedWebhook:
      description: Complete webhook payload for reaction.removed events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ReactionRemovedEvent'
    ParticipantAddedEvent:
      type: object
      description: Payload for participant.added webhook events
      required:
        - handle
      properties:
        chat_id:
          type: string
          description: Chat identifier (UUID) of the group chat
          example: 550e8400-e29b-41d4-a716-446655440000
        handle:
          type: string
          description: 'DEPRECATED: Use participant instead. Handle (phone number or email address) of the added participant.'
          deprecated: true
          example: '+14155559876'
        participant:
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: The added participant as a full handle object
          example:
            id: 550e8400-e29b-41d4-a716-446655440011
            handle: '+14155559876'
            is_me: false
            service: iMessage
            status: active
            joined_at: '2025-11-23T17:40:00.000Z'
            left_at: null
        added_at:
          type: string
          format: date-time
          description: When the participant was added
          example: '2025-11-23T17:40:00Z'
    ParticipantAddedWebhook:
      description: Complete webhook payload for participant.added events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ParticipantAddedEvent'
    ParticipantRemovedEvent:
      type: object
      description: Payload for participant.removed webhook events
      required:
        - handle
      properties:
        chat_id:
          type: string
          description: Chat identifier (UUID) of the group chat
          example: 550e8400-e29b-41d4-a716-446655440000
        handle:
          type: string
          description: 'DEPRECATED: Use participant instead. Handle (phone number or email address) of the removed participant.'
          deprecated: true
          example: '+14155559876'
        participant:
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: The removed participant as a full handle object
          example:
            id: 550e8400-e29b-41d4-a716-446655440011
            handle: '+14155559876'
            is_me: false
            service: iMessage
            status: removed
            joined_at: '2025-11-23T17:30:00.000Z'
            left_at: '2025-11-23T17:45:00.000Z'
        removed_at:
          type: string
          format: date-time
          description: When the participant was removed
          example: '2025-11-23T17:45:00Z'
    ParticipantRemovedWebhook:
      description: Complete webhook payload for participant.removed events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ParticipantRemovedEvent'
    ChatGroupNameUpdatedEvent:
      type: object
      description: Payload for chat.group_name_updated webhook events
      required:
        - chat_id
        - updated_at
      properties:
        chat_id:
          type: string
          description: Chat identifier (UUID) of the group chat
          example: 550e8400-e29b-41d4-a716-446655440000
        old_value:
          type: string
          nullable: true
          description: Previous group name (null if no previous name)
          example: Old Group Name
        new_value:
          type: string
          nullable: true
          description: New group name (null if the name was removed)
          example: New Group Name
        changed_by_handle:
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          nullable: true
          description: The handle who made the change.
          example:
            id: 550e8400-e29b-41d4-a716-446655440011
            handle: '+14155559876'
            is_me: false
            service: iMessage
            status: active
            joined_at: '2025-11-23T17:30:00.000Z'
            left_at: null
        updated_at:
          type: string
          format: date-time
          description: When the update occurred
          example: '2025-11-23T17:50:00Z'
    ChatGroupNameUpdatedWebhook:
      description: Complete webhook payload for chat.group_name_updated events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ChatGroupNameUpdatedEvent'
    ChatGroupIconUpdatedEvent:
      type: object
      description: Payload for chat.group_icon_updated webhook events
      required:
        - chat_id
        - updated_at
      properties:
        chat_id:
          type: string
          description: Chat identifier (UUID) of the group chat
          example: 550e8400-e29b-41d4-a716-446655440000
        old_value:
          type: string
          nullable: true
          description: Previous icon URL (null if no previous icon)
          example: https://example.com/old-icon.png
        new_value:
          type: string
          nullable: true
          description: New icon URL (null if the icon was removed)
          example: https://example.com/new-icon.png
        changed_by_handle:
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          nullable: true
          description: The handle who made the change.
          example:
            id: 550e8400-e29b-41d4-a716-446655440011
            handle: '+14155559876'
            is_me: false
            service: iMessage
            status: active
            joined_at: '2025-11-23T17:30:00.000Z'
            left_at: null
        updated_at:
          type: string
          format: date-time
          description: When the update occurred
          example: '2025-11-23T17:50:00Z'
    ChatGroupIconUpdatedWebhook:
      description: Complete webhook payload for chat.group_icon_updated events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ChatGroupIconUpdatedEvent'
    ChatGroupNameUpdateFailedEvent:
      type: object
      description: |
        Error details for chat.group_name_update_failed webhook events.
        See [WebhookErrorCode](#/components/schemas/WebhookErrorCode) for the full error code reference.
      required:
        - chat_id
        - error_code
        - failed_at
      properties:
        chat_id:
          type: string
          description: Chat identifier (UUID) of the group chat
          example: 550e8400-e29b-41d4-a716-446655440000
        error_code:
          $ref: '#/components/schemas/WebhookErrorCode'
        failed_at:
          type: string
          format: date-time
          description: When the failure was detected
          example: '2025-11-23T17:55:00Z'
    ChatGroupNameUpdateFailedWebhook:
      description: Complete webhook payload for chat.group_name_update_failed events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ChatGroupNameUpdateFailedEvent'
    ChatGroupIconUpdateFailedEvent:
      type: object
      description: |
        Error details for chat.group_icon_update_failed webhook events.
        See [WebhookErrorCode](#/components/schemas/WebhookErrorCode) for the full error code reference.
      required:
        - chat_id
        - error_code
        - failed_at
      properties:
        chat_id:
          type: string
          description: Chat identifier (UUID) of the group chat
          example: 550e8400-e29b-41d4-a716-446655440000
        error_code:
          $ref: '#/components/schemas/WebhookErrorCode'
        failed_at:
          type: string
          format: date-time
          description: When the failure was detected
          example: '2025-11-23T17:55:00Z'
    ChatGroupIconUpdateFailedWebhook:
      description: Complete webhook payload for chat.group_icon_update_failed events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ChatGroupIconUpdateFailedEvent'
    ChatCreatedEvent:
      type: object
      description: Payload for chat.created webhook events. Matches GET /v3/chats/{chatId} response.
      required:
        - id
        - display_name
        - handles
        - is_group
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the chat
          example: 550e8400-e29b-41d4-a716-446655440000
        display_name:
          type: string
          nullable: true
          description: Display name for the chat. Defaults to a comma-separated list of recipient handles. Can be updated for group chats.
          example: +14155551234, +14155559876
        service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          description: Service type for the chat
          example: iMessage
          nullable: true
        handles:
          type: array
          description: List of chat participants with full handle details. Always contains at least two handles (your phone number and the other participant).
          items:
            $ref: '#/components/schemas/ChatHandle'
          example:
            - id: 550e8400-e29b-41d4-a716-446655440010
              handle: '+14155551234'
              is_me: true
              service: iMessage
              status: active
              joined_at: '2025-11-23T17:30:00.000Z'
              left_at: null
            - id: 550e8400-e29b-41d4-a716-446655440011
              handle: '+14155559876'
              is_me: false
              service: iMessage
              status: active
              joined_at: '2025-11-23T17:30:00.000Z'
              left_at: null
        is_group:
          type: boolean
          description: Whether this is a group chat
          default: false
          example: true
        created_at:
          type: string
          format: date-time
          description: When the chat was created
          example: '2025-11-23T17:30:00Z'
        updated_at:
          type: string
          format: date-time
          description: When the chat was last updated
          example: '2025-11-23T17:30:00Z'
    ChatCreatedWebhook:
      description: Complete webhook payload for chat.created events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ChatCreatedEvent'
    ChatTypingIndicatorStartedEvent:
      type: object
      description: Payload for chat.typing_indicator.started webhook events
      required:
        - chat_id
      properties:
        chat_id:
          type: string
          format: uuid
          description: Chat identifier
          example: 550e8400-e29b-41d4-a716-446655440000
    ChatTypingIndicatorStartedWebhook:
      description: Complete webhook payload for chat.typing_indicator.started events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ChatTypingIndicatorStartedEvent'
    ChatTypingIndicatorStoppedEvent:
      type: object
      description: Payload for chat.typing_indicator.stopped webhook events
      required:
        - chat_id
      properties:
        chat_id:
          type: string
          format: uuid
          description: Chat identifier
          example: 550e8400-e29b-41d4-a716-446655440000
    ChatTypingIndicatorStoppedWebhook:
      description: Complete webhook payload for chat.typing_indicator.stopped events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/ChatTypingIndicatorStoppedEvent'
    MessageEditedEventV2:
      type: object
      description: |
        Payload for `message.edited` events (2026-02-03 format).

        Describes which part of a message was edited and when. Only text parts can be edited.
        Only available for subscriptions using `webhook_version: "2026-02-03"`.
      required:
        - chat
        - direction
        - edited_at
        - id
        - part
        - sender_handle
      properties:
        chat:
          type: object
          description: Chat context
          required:
            - id
            - is_group
            - owner_handle
          properties:
            id:
              type: string
              format: uuid
              description: Chat identifier
            is_group:
              type: boolean
              description: Whether this is a group chat
            owner_handle:
              allOf:
                - $ref: '#/components/schemas/ChatHandle'
              description: The handle that owns this chat (your phone number)
        direction:
          type: string
          enum:
            - outbound
            - inbound
          description: '"outbound" if you sent the original message, "inbound" if you received it'
        edited_at:
          type: string
          format: date-time
          description: When the edit occurred
          example: '2026-03-05T02:12:46.487Z'
        id:
          type: string
          format: uuid
          description: Message identifier
        part:
          type: object
          description: The edited part
          required:
            - index
            - text
          properties:
            index:
              type: integer
              description: Zero-based index of the edited part within the message
              example: 4
            text:
              type: string
              description: New text content of the part
              example: Edited message text
        sender_handle:
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: The handle that sent (and edited) this message
    MessageEditedWebhookV2:
      description: Complete webhook payload for message.edited events (2026-02-03 format only)
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageEditedEventV2'
    PhoneNumberStatusUpdatedEvent:
      type: object
      description: Payload for phone_number.status_updated webhook events
      required:
        - phone_number
        - previous_status
        - new_status
        - changed_at
      properties:
        phone_number:
          type: string
          description: Phone number in E.164 format
          example: '+15551234567'
        previous_status:
          type: string
          description: The previous service status
          enum:
            - ACTIVE
            - FLAGGED
          example: ACTIVE
        new_status:
          type: string
          description: The new service status
          enum:
            - ACTIVE
            - FLAGGED
          example: FLAGGED
        changed_at:
          type: string
          format: date-time
          description: When the status change occurred
          example: '2024-01-15T10:30:00Z'
    PhoneNumberStatusUpdatedWebhook:
      description: Complete webhook payload for phone_number.status_updated events
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            event_type:
              type: string
              description: The type of event
              example: phone_number.status_updated
            data:
              $ref: '#/components/schemas/PhoneNumberStatusUpdatedEvent'
    MessagePayload:
      type: object
      description: Message content nested within webhook events
      properties:
        id:
          type: string
          format: uuid
          description: Message identifier
          example: 550e8400-e29b-41d4-a716-446655440001
        parts:
          type: array
          description: Message content parts (text and/or media)
          items:
            oneOf:
              - $ref: '#/components/schemas/schemas-TextPartResponse'
              - $ref: '#/components/schemas/schemas-MediaPartResponse'
              - $ref: '#/components/schemas/schemas-LinkPartResponse'
        effect:
          $ref: '#/components/schemas/schemas-MessageEffect'
        reply_to:
          type: object
          description: Reference to the message this is replying to
          properties:
            message_id:
              type: string
              format: uuid
              description: The ID of the message being replied to
              example: 550e8400-e29b-41d4-a716-446655440000
            part_index:
              type: integer
              format: int32
              minimum: 0
              description: Index of the message part being replied to (0-based)
              example: 0
        is_delivered:
          type: boolean
          description: Whether the message has been delivered
          example: false
        is_read:
          type: boolean
          description: Whether the message has been read
          example: false
        sent_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was sent
          example: '2024-01-15T10:30:05Z'
        delivered_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was delivered
          example: null
        read_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was read
          example: null
        created_at:
          type: string
          format: date-time
          description: When the message record was created
          example: '2024-01-15T10:30:00Z'
        updated_at:
          type: string
          format: date-time
          description: When the message record was last updated
          example: '2024-01-15T10:30:00Z'
    MessageEvent:
      type: object
      description: Unified payload for message.sent and message.received webhook events (2025-01-01 format)
      properties:
        chat_id:
          type: string
          format: uuid
          description: Chat identifier
          example: 550e8400-e29b-41d4-a716-446655440000
        is_group:
          type: boolean
          description: Whether this is a group chat
          example: false
        idempotency_key:
          type: string
          nullable: true
          description: Idempotency key for the message. Used for deduplication of outbound messages.
          example: unique-request-key-12345
        from:
          type: string
          description: 'DEPRECATED: Use from_handle instead. Phone number or email address of the message sender.'
          example: '+14155551234'
          deprecated: true
        from_handle:
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: The sender of this message as a full handle object
        message:
          $ref: '#/components/schemas/MessagePayload'
        recipient_phone:
          type: string
          nullable: true
          description: 'DEPRECATED: Use recipient_handle instead. Our phone number that received the message. Null for sent events.'
          example: '+14155551234'
          deprecated: true
        recipient_handle:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/ChatHandle'
          description: Our phone number that received the message as a full handle object. Null for sent events.
        is_from_me:
          type: boolean
          description: Whether the message was sent by us (true for sent events, false for received events)
          example: true
        received_at:
          type: string
          format: date-time
          nullable: true
          description: When the message was received. Null for sent events.
          example: '2025-11-23T17:29:55Z'
        service:
          allOf:
            - $ref: '#/components/schemas/ServiceType'
          nullable: true
          description: The service used to send/receive the message
          example: iMessage
        preferred_service:
          allOf:
            - $ref: '#/components/schemas/ServicePreferenceType'
          nullable: true
          description: The service that was requested when sending the message. Null for received events.
          example: iMessage
    MessageSentWebhook:
      description: Complete webhook payload for message.sent events (2025-01-01 format)
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageEvent'
    MessageReceivedWebhook:
      description: Complete webhook payload for message.received events (2025-01-01 format)
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageEvent'
    MessageReadEvent:
      description: Payload for message.read webhook events (2025-01-01 format). Extends MessageEvent with read_at and message_id.
      allOf:
        - $ref: '#/components/schemas/MessageEvent'
        - type: object
          required:
            - read_at
          properties:
            message_id:
              type: string
              description: Message identifier (UUID)
              example: 550e8400-e29b-41d4-a716-446655440001
            read_at:
              type: string
              format: date-time
              description: When the message was read
              example: '2025-11-23T17:35:00Z'
    MessageReadWebhook:
      description: Complete webhook payload for message.read events (2025-01-01 format)
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageReadEvent'
    MessageDeliveredEvent:
      description: Payload for message.delivered webhook events (2025-01-01 format). Extends MessageEvent with delivered_at and message_id.
      allOf:
        - $ref: '#/components/schemas/MessageEvent'
        - type: object
          required:
            - delivered_at
          properties:
            message_id:
              type: string
              description: Message identifier (UUID)
              example: 550e8400-e29b-41d4-a716-446655440001
            delivered_at:
              type: string
              format: date-time
              description: When the message was delivered to the recipient's device
              example: '2025-11-23T17:32:00Z'
    MessageDeliveredWebhook:
      description: Complete webhook payload for message.delivered events (2025-01-01 format)
      allOf:
        - $ref: '#/components/schemas/WebhookEnvelopeBase'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/MessageDeliveredEvent'
x-tagGroups:
  - name: Messaging
    tags:
      - Chats
      - Messages
      - Attachments
      - Phone Numbers
      - Capability Checks
  - name: Webhooks
    tags:
      - Webhooks
      - Webhook Events
      - '2025-01-01'
      - '2026-02-03'
