> ## Documentation Index
> Fetch the complete documentation index at: https://howto.paigeme.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Send a message (session, template, or interactive)

> Dispatch by `type`. Session types (`text|image|video|audio|document|location`) and `interactive` (button/list/carousel) are gated by the 24h customer-service window (closed → `409 outside_24h_window`); `template` is exempt. Supports the optional `Idempotency-Key` header (safe retries — a completed key replays the stored response). Reserves 1 unit of the daily send quota.

**What you must send.** Beyond `type`, the required fields are conditional, so they cannot all be marked required in the schema below:

- **Session and `interactive` types** need exactly one recipient — `conversationId` **or** `phone` (E.164, e.g. `+27821234567`) **or** `bsuid` — plus `content`. For `text`, `content` is the message body; for media types it is the URL or media id; for `interactive` it is the button/list/carousel payload. Omit the recipient → `400 missing_recipient`.
- **`template`** needs a recipient spelled `to` **or** `phone` (an E.164 number — `conversationId`/`bsuid` are not accepted on this branch), plus a template name and a language.
- **Template name and language each accept two spellings** — `name` or `templateName`, and `language` or `languageCode` — because the endpoint takes both the friendly and the strict form. Send one of each; if both are present the friendly spelling (`name` / `language`) wins. Missing → `400 missing_template_name` / `400 missing_language`.
- **Template variables** go in either raw Meta `components` **or** the structured `header`/`body`/`footer`/`buttons` form. If you send both, raw `components` takes precedence and the structured form is ignored. A variable count that disagrees with the approved template is `400 template_variable_mismatch`.

**Required scope:** `messages:send`



## OpenAPI

````yaml /api-reference/openapi.json post /v1/messages
openapi: 3.1.0
info:
  title: Paige API
  version: 1.0.0
  description: >-
    The Paige public REST API (`/v1`). Build WhatsApp automations against your
    Paige project: send messages, manage templates, read conversations, tag
    contacts, assemble broadcasts, edit bot code + flows, and register signed
    webhooks.


    ## Authentication

    Every `/v1` request authenticates with a project API key: `Authorization:
    Bearer pk_live_…`. Mint and scope keys in **Settings → API keys**. A key
    carries its own project context, so `/v1` paths never include a project id.


    ## Scopes

    Each endpoint requires one or more scopes (see each operation, and the
    `x-required-scopes` extension). Grant a key only the scopes it needs. A key
    missing a required scope gets `403 insufficient_scope`.


    ## Response envelope

    Success: `{ "success": true, "data": … }`. Error: `{ "success": false,
    "error": { "code", "message" }, "request_id" }`. Some errors add
    `error.details` with structured extras (e.g. `quota_exceeded` carries
    `limit` + `resetAt`) — read it defensively, its keys depend on the code. The
    `request_id` is also returned as the `X-Request-Id` header on every
    response.


    ## Rate limits & quota

    Requests are throttled per key (`RateLimit-*` headers; exceed → `429
    rate_limited`). WhatsApp sends also draw down a daily quota (exceed → `429
    quota_exceeded`). Both carry `Retry-After`.


    ## Idempotency

    Send an `Idempotency-Key` header on `POST /v1/messages` to make retries safe
    — a completed key replays the stored response and never re-sends.


    ## Webhook signatures

    Outbound webhook deliveries carry `X-Paige-Signature:
    t=<unix>,v1=<hmac_sha256>`. Verify with the reference `verifyPaigeSignature`
    helper (constant-time HMAC over `"<t>.<rawBody>"`, 300s tolerance).
  contact:
    name: Paige
    url: https://paigeme.dev
servers:
  - url: https://api.paigeme.dev
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: Meta
    description: Smoke / diagnostics.
  - name: Messages
    description: Send messages and read delivery status + media.
  - name: Conversations
    description: List conversations, read messages, set state.
  - name: Contacts
    description: Read contact messages; update names, tags, attributes.
  - name: Templates
    description: WhatsApp message template CRUD.
  - name: Broadcasts
    description: Segments + broadcast assembly (always pending_approval).
  - name: Build
    description: Read/edit bot code, deploy, read/generate/update flows.
  - name: Webhooks
    description: Register signed outbound webhooks.
paths:
  /v1/messages:
    post:
      tags:
        - Messages
      summary: Send a message (session, template, or interactive)
      description: >-
        Dispatch by `type`. Session types
        (`text|image|video|audio|document|location`) and `interactive`
        (button/list/carousel) are gated by the 24h customer-service window
        (closed → `409 outside_24h_window`); `template` is exempt. Supports the
        optional `Idempotency-Key` header (safe retries — a completed key
        replays the stored response). Reserves 1 unit of the daily send quota.


        **What you must send.** Beyond `type`, the required fields are
        conditional, so they cannot all be marked required in the schema below:


        - **Session and `interactive` types** need exactly one recipient —
        `conversationId` **or** `phone` (E.164, e.g. `+27821234567`) **or**
        `bsuid` — plus `content`. For `text`, `content` is the message body; for
        media types it is the URL or media id; for `interactive` it is the
        button/list/carousel payload. Omit the recipient → `400
        missing_recipient`.

        - **`template`** needs a recipient spelled `to` **or** `phone` (an E.164
        number — `conversationId`/`bsuid` are not accepted on this branch), plus
        a template name and a language.

        - **Template name and language each accept two spellings** — `name` or
        `templateName`, and `language` or `languageCode` — because the endpoint
        takes both the friendly and the strict form. Send one of each; if both
        are present the friendly spelling (`name` / `language`) wins. Missing →
        `400 missing_template_name` / `400 missing_language`.

        - **Template variables** go in either raw Meta `components` **or** the
        structured `header`/`body`/`footer`/`buttons` form. If you send both,
        raw `components` takes precedence and the structured form is ignored. A
        variable count that disagrees with the approved template is `400
        template_variable_mismatch`.


        **Required scope:** `messages:send`
      operationId: sendMessage
      parameters:
        - schema:
            type: string
            maxLength: 255
            description: Opt-in idempotency key for safe retries of this send.
          required: false
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: >-
              Target project id, for an MCP OAuth bearer (`mcp_at_…`) attached
              to more than one project — get ids from `GET /v1/projects`.
              Matched case-insensitively.


              Omit it and a READ falls back to the connection's default project;
              a **mutation** (any non-GET) on a connection with 2+ projects is
              rejected with `400 project_required` — a write is never defaulted
              to a guessed project. A connection with exactly one project never
              needs the header.


              Scopes are checked against the SELECTED project only, never a
              union across the connection.


              For a `pk_` API key the header selects nothing — one key is one
              project's context — but it IS validated: omit it and the key's own
              project is used, send it and it must name that project, otherwise
              the call is rejected with `403 project_not_attached` (a blank
              value is `400 invalid_project_header`, as above).
          required: false
          name: X-Paige-Project
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              anyOf:
                - type: object
                  properties:
                    conversationId:
                      anyOf:
                        - type: string
                          minLength: 1
                        - type: number
                    phone:
                      type: string
                    bsuid:
                      type: string
                    type:
                      type: string
                      enum:
                        - text
                        - image
                        - video
                        - audio
                        - document
                        - location
                    content: {}
                  required:
                    - type
                - type: object
                  properties:
                    to:
                      type: string
                    phone:
                      type: string
                    name:
                      type: string
                    templateName:
                      type: string
                    language:
                      type: string
                    languageCode:
                      type: string
                    components:
                      type: array
                      items:
                        type: object
                        additionalProperties: {}
                    header:
                      type: object
                      properties:
                        type:
                          type: string
                        text:
                          type: string
                        variables:
                          type: array
                          items:
                            anyOf:
                              - type: string
                              - type: number
                        link:
                          type: string
                        id:
                          type: string
                    body:
                      type: object
                      properties:
                        variables:
                          type: array
                          items:
                            anyOf:
                              - type: string
                              - type: number
                    footer:
                      type: object
                      additionalProperties: {}
                    buttons:
                      type: array
                      items:
                        type: object
                        properties:
                          sub_type:
                            type: string
                          type:
                            type: string
                          index:
                            anyOf:
                              - type: number
                              - type: string
                          payload:
                            type: string
                          text:
                            type: string
                    type:
                      type: string
                      enum:
                        - template
                  required:
                    - type
                - type: object
                  properties:
                    conversationId:
                      anyOf:
                        - type: string
                          minLength: 1
                        - type: number
                    phone:
                      type: string
                      pattern: ^\+?\d{7,15}$
                    bsuid:
                      type: string
                    content:
                      oneOf:
                        - type: object
                          properties:
                            type:
                              type: string
                              enum:
                                - button
                            header:
                              type: object
                              properties:
                                type:
                                  type: string
                                  enum:
                                    - text
                                    - image
                                    - video
                                    - document
                              required:
                                - type
                            body:
                              type: object
                              properties:
                                text:
                                  type: string
                                  minLength: 1
                                  maxLength: 1024
                              required:
                                - text
                            footer:
                              type: object
                              properties:
                                text:
                                  type: string
                                  minLength: 1
                                  maxLength: 60
                              required:
                                - text
                            action:
                              type: object
                              properties:
                                buttons:
                                  type: array
                                  items:
                                    type: object
                                    properties:
                                      type:
                                        type: string
                                        enum:
                                          - reply
                                      reply:
                                        type: object
                                        properties:
                                          id:
                                            type: string
                                            minLength: 1
                                            maxLength: 256
                                          title:
                                            type: string
                                            minLength: 1
                                            maxLength: 20
                                        required:
                                          - id
                                          - title
                                    required:
                                      - type
                                      - reply
                              required:
                                - buttons
                          required:
                            - type
                            - body
                            - action
                        - type: object
                          properties:
                            type:
                              type: string
                              enum:
                                - list
                            header:
                              type: object
                              properties:
                                type:
                                  type: string
                                  enum:
                                    - text
                                    - image
                                    - video
                                    - document
                              required:
                                - type
                            body:
                              type: object
                              properties:
                                text:
                                  type: string
                                  minLength: 1
                                  maxLength: 1024
                              required:
                                - text
                            footer:
                              type: object
                              properties:
                                text:
                                  type: string
                                  minLength: 1
                                  maxLength: 60
                              required:
                                - text
                            action:
                              type: object
                              properties:
                                button:
                                  type: string
                                  minLength: 1
                                  maxLength: 20
                                sections:
                                  type: array
                                  items:
                                    type: object
                                    properties:
                                      title:
                                        type: string
                                        maxLength: 24
                                      rows:
                                        type: array
                                        items:
                                          type: object
                                          properties:
                                            id:
                                              type: string
                                              minLength: 1
                                              maxLength: 200
                                            title:
                                              type: string
                                              minLength: 1
                                              maxLength: 24
                                            description:
                                              type: string
                                              maxLength: 72
                                          required:
                                            - id
                                            - title
                                    required:
                                      - rows
                              required:
                                - button
                                - sections
                          required:
                            - type
                            - body
                            - action
                        - type: object
                          properties:
                            type:
                              type: string
                              enum:
                                - carousel
                            body:
                              type: object
                              properties:
                                text:
                                  type: string
                                  minLength: 1
                                  maxLength: 1024
                              required:
                                - text
                            action:
                              type: object
                              properties:
                                cards:
                                  type: array
                                  items:
                                    type: object
                                    properties:
                                      card_index:
                                        type: integer
                                        minimum: 0
                                      body:
                                        type: object
                                        properties:
                                          text:
                                            type: string
                                            minLength: 1
                                            maxLength: 1024
                                        required:
                                          - text
                                      action:
                                        type: object
                                        properties:
                                          buttons:
                                            type: array
                                            items:
                                              type: object
                                              additionalProperties: {}
                                        required:
                                          - buttons
                                    required:
                                      - action
                              required:
                                - cards
                          required:
                            - type
                            - action
                    type:
                      type: string
                      enum:
                        - interactive
                  required:
                    - content
                    - type
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    enum:
                      - true
                  data:
                    type: object
                    properties:
                      id:
                        type:
                          - string
                          - 'null'
                      status:
                        type: string
                    required:
                      - id
                      - status
                required:
                  - success
                  - data
        '400':
          description: >-
            Validation / bad request (e.g. `invalid_request`, `invalid_cursor`,
            `outside_24h_window` is 409). Also `invalid_project_header` (any
            credential: `X-Paige-Project` was sent but blank) and, for
            multi-project MCP bearers, `project_required` (a mutation, or a read
            with no default, and no `X-Paige-Project`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: >-
            Missing or invalid API key (`api_key_required` / `invalid_api_key` /
            `invalid_token` for a revoked or expired OAuth bearer).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: Out of credits (`insufficient_credits`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >-
            Key lacks the required scope, project inactive, or a write targets a
            platform-protected file (`insufficient_scope` / `project_inactive` /
            `subscription_inactive` / `protected_file`). Also
            `project_not_attached` (the `X-Paige-Project` project is not one
            this credential may act on — not attached to the MCP connection, or
            not the `pk_` key's own project; identical response whether or not
            it exists) and, for MCP bearers, `no_project_access` (the connection
            has no projects attached).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Resource not found (`not_found` / `*_not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            Conflict — 24h window closed (`outside_24h_window`) or idempotency
            (`idempotency_in_progress` / `idempotency_key_mismatch`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: >-
            Rate limit (`rate_limited`) or daily send quota (`quota_exceeded`)
            exceeded. Carries `RateLimit-*` + `Retry-After` headers.
          headers:
            RateLimit-Limit:
              description: Requests permitted in the current window.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests remaining in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Unexpected server error (`server_error`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          $ref: '#/components/schemas/ApiError'
        request_id:
          type: string
          description: Per-request id, also returned as the X-Request-Id header.
      required:
        - success
        - error
        - request_id
    ApiError:
      type: object
      properties:
        code:
          type: string
          description: Stable, machine-readable error code.
          example: invalid_request
        message:
          type: string
          description: Human-readable message (safe to surface).
          example: Invalid request body
        details:
          type: object
          properties: {}
          description: >-
            Structured extras for codes that carry them — the keys depend on
            `code`. `quota_exceeded` carries `limit` + `resetAt`; most errors
            omit this field entirely.
      required:
        - code
        - message
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Project API key issued in Settings → API keys. Send it as
        `Authorization: Bearer pk_live_…`.

````