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

# Send a template message

> Sends an approved template to a phone number, and opens the
conversation when none exists. This is the endpoint behind a
"message on WhatsApp" button in your own system.

One call does all of it. Kai finds or creates the conversation for
the number, finds or creates the contact behind it, fills the
template from its mirror, and sends. Name a teammate in `assignee`
to also assign the conversation to them.

You must set `opt_in_confirmed` to `true` on every call. This is
your assertion that the person agreed to receive messages from you.
Kai stores it on the message.

Send an `Idempotency-Key` header. When a call with the same key
already succeeded, Kai returns the stored response and does not send
again. A failed call releases the key, so a retry runs.

When the conversation is not assigned, Kai's own reply policy stays
in charge of the customer's answer. Assign the conversation to a
teammate when a person must handle what comes back.




## OpenAPI

````yaml /openapi.yaml post /conversations/outbound
openapi: 3.1.0
info:
  title: Kai API
  version: 1.1.0
  description: |
    Push, update, assign and export the contacts of one Kai workspace. Read
    a contact's conversations, list the approved WhatsApp templates, send a
    template message to a customer, and assign the conversation to a
    teammate.

    Every request needs an API key. Make a key in Kai, under
    **Settings → API**. Send the key in the `Authorization` header.

    The API works on one workspace. The key decides which workspace, so no
    request carries a workspace id.
  contact:
    name: Kai support
    url: https://kaisupport.com
servers:
  - url: https://kaisupport.com/api/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Contacts
    description: Customer profiles, their attributes and their account owners.
  - name: Attributes
    description: The attributes that the workspace keeps on every contact.
  - name: Members
    description: The teammates of the workspace. Read-only.
  - name: Conversations
    description: A contact's message threads, and who they are assigned to.
  - name: Templates
    description: The approved WhatsApp message templates of a channel.
  - name: Channels
    description: The connected messaging channels. Read-only.
paths:
  /conversations/outbound:
    post:
      tags:
        - Conversations
      summary: Send a template message
      description: |
        Sends an approved template to a phone number, and opens the
        conversation when none exists. This is the endpoint behind a
        "message on WhatsApp" button in your own system.

        One call does all of it. Kai finds or creates the conversation for
        the number, finds or creates the contact behind it, fills the
        template from its mirror, and sends. Name a teammate in `assignee`
        to also assign the conversation to them.

        You must set `opt_in_confirmed` to `true` on every call. This is
        your assertion that the person agreed to receive messages from you.
        Kai stores it on the message.

        Send an `Idempotency-Key` header. When a call with the same key
        already succeeded, Kai returns the stored response and does not send
        again. A failed call releases the key, so a retry runs.

        When the conversation is not assigned, Kai's own reply policy stays
        in charge of the customer's answer. Assign the conversation to a
        teammate when a person must handle what comes back.
      operationId: sendOutbound
      parameters:
        - name: Idempotency-Key
          in: header
          description: A unique string per send, at most 200 characters.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutboundSend'
            example:
              to:
                phone: +90 532 111 22 33
              contact:
                name: Ayşe Yılmaz
              template:
                name: randevu_hatirlatma
                language: tr
                variables:
                  - Ayşe Hanım
              assignee: baran@example.com
              opt_in_confirmed: true
      responses:
        '201':
          description: The message was sent.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      conversation:
                        $ref: '#/components/schemas/Conversation'
                      existing_conversation:
                        type: boolean
                        description: >-
                          True when the thread already existed and the message
                          joined it.
                      message:
                        $ref: '#/components/schemas/Message'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: |
            Two causes, told apart by `error.type`. `identity_conflict`: the
            number belongs to a conversation with a different contact than
            `to.contact_id` names. `idempotency_in_flight`: a call with this
            `Idempotency-Key` is still running.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: |
            The send was refused before it left. `error.type` names the
            cause: `template_missing`, `template_unapproved`,
            `variables_missing`, `variables_unknown`, `variables_not_named`,
            `no_phone`, `channel_cannot_send`, `recipient_undeliverable` or
            `idempotency_mismatch`.
            A variable error names the variables that caused it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: WhatsApp refused the send. The message carries Meta's reason.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    OutboundSend:
      type: object
      required:
        - to
        - template
        - opt_in_confirmed
      properties:
        channel_id:
          type: string
          format: uuid
          description: Optional when the workspace has one WhatsApp channel.
        to:
          type: object
          description: Exactly one of `phone` or `contact_id`.
          properties:
            phone:
              type: string
              description: The customer's number, best in E.164 form.
            country:
              type: string
              description: An ISO country code, for a national number without `+`.
            contact_id:
              type: string
              format: uuid
              description: A contact whose phone number Kai already holds.
        contact:
          type: object
          description: Profile fields for a contact that this send creates.
          properties:
            name:
              type:
                - string
                - 'null'
        template:
          type: object
          required:
            - name
            - language
          properties:
            name:
              type: string
            language:
              type: string
            variables:
              description: |
                The values of the template, in one of two shapes. All of them
                are required, and Kai refuses an empty value.

                An **object** keys the values by the address of each
                variable, for a template that names its variables
                (`{{customer_name}}`). The address of a title variable has
                `header.` in front of it. Prefer this shape: Kai puts the
                values in the order of the template, so an edit at Meta that
                moves a variable cannot swap your values.

                An **array** holds the values in the order of the `variables`
                field of `GET /templates`, the title variables first. A
                template that numbers all of its variables (`{{1}}`) accepts
                only this shape.
              oneOf:
                - type: object
                  additionalProperties:
                    type: string
                - type: array
                  items:
                    type: string
              example:
                header.konu: Ön görüşme
                customer_name: Gülsüm Bodur
                consultant_name: Baran Demir
        assignee:
          type:
            - string
            - 'null'
          description: >-
            A member id or a member email. The conversation is assigned to them
            after the send.
        opt_in_confirmed:
          type: boolean
          description: >-
            Must be `true`. Your assertion that this person opted in to your
            messages.
    Conversation:
      allOf:
        - $ref: '#/components/schemas/ConversationSummary'
        - type: object
          properties:
            contact_id:
              type:
                - string
                - 'null'
              format: uuid
            source_type:
              type:
                - string
                - 'null'
              description: The sub-channel, such as `whatsapp` or `email`.
    Message:
      type:
        - object
        - 'null'
      description: The stored message, or `null` when Kai could not read it back.
      properties:
        id:
          type: string
          format: uuid
        external_id:
          type: string
          description: The provider's message id.
        text:
          type: string
          description: The text the customer receives, with the variables filled.
        delivery_status:
          type:
            - string
            - 'null'
          enum:
            - sent
            - delivered
            - read
            - failed
            - null
        ts:
          type: string
          format: date-time
    Error:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
    ConversationSummary:
      type: object
      required:
        - id
        - channel_id
        - status
        - created_at
      properties:
        id:
          type: string
          format: uuid
        channel_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - open
            - ai
            - human
            - closed
            - archived
          description: |
            `open`, `ai` and `human` are live states. `closed` rests until
            the customer writes again. `archived` is final.
        assigned_member_id:
          type:
            - string
            - 'null'
          format: uuid
          description: The teammate who holds this thread, or `null` for the shared queue.
        assigned_via:
          type:
            - string
            - 'null'
          enum:
            - member
            - api
            - null
          description: |
            Who made the assignment. `member` is a person in Kai. `api` is a
            call to this API. `null` is Kai's own routing, or an old row.
        last_message_preview:
          type:
            - string
            - 'null'
        last_message_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
    ErrorBody:
      type: object
      properties:
        type:
          type: string
          description: A stable machine name for the error.
        message:
          type: string
          description: One sentence for a human reader.
        details:
          description: The attributes or the identities that caused the error.
  responses:
    BadRequest:
      description: Kai could not read the request.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: invalid_request
              message: >-
                Send at least one of "external_id", "email", "phone" or
                "identities" so the contact can be matched.
    Unauthorized:
      description: The API key is missing or not valid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: unauthorized
              message: That API key is not valid.
    Forbidden:
      description: The API key does not have the permission that this endpoint needs.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: forbidden
              message: This key does not have the "contacts:write" permission.
    NotFound:
      description: There is no such record in this workspace.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: not_found
              message: No such contact.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Send your API key as `Authorization: Bearer kai_live_...`.

        Make, edit and revoke keys in Kai, under **Settings → API**. Kai stores
        only a hash of the key, so the full key is shown one time. You can
        change the name and the permissions of a key later without a new key.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.