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

# Send a template message

> Send an approved WhatsApp template to a customer.

A send that is taken answers `202` with status `accepted` as soon as the
sending service has it — the reply carries no delivery outcome. Poll
`GET /template-messages/{event_id}` for what became of it; no webhook
reports a send outcome today.

Idempotent on `event_id`: repeating one returns the state of the first
request and never produces a second message.

A `422` names the offending field: a phone outside strict E.164, a template
that is not approved for this merchant in that language, or a missing
template variable. It also covers the two account-level refusals that no
payload can fix — an account that is not on the conversational rail this
API sends from, and an account with no connected WhatsApp sender number —
each carrying its own `type`. Neither is worth a retry.

An event older than the freshness window, or one whose conversation is
paused, is not an error either: it answers `200` with status `discarded`
and the reason in `status_detail`.

A `402` means the account cannot pay for the message and nothing was sent.
`insufficient_funds` and `wallet_sends_paused` clear as soon as the account
adds funds, and the same `event_id` may be sent again once it has;
`wallet_currency_mismatch` needs us. Retrying without adding funds returns
the same answer.



## OpenAPI

````yaml /openapi.json post /v1/template-messages
openapi: 3.1.0
info:
  title: YV Business API
  description: >-
    Public API for YaVendio merchants to access orders, product catalog,
    conversations, contacts and company profile, and to send WhatsApp template
    messages.


    ## Authentication


    All endpoints require a valid API key sent via the `X-API-Key` header. API
    keys are created and managed from the [YaVendio Dashboard](https://ya.onl)
    under **Settings → API Keys**.


    Use the **Authorize** button above to set your key for all requests.


    Every endpoint declares the scope it requires (e.g. `orders:read`,
    `products:write`, `conversations:read`, `contacts:read`, `company:read`,
    `outbound:send`, `outbound:read`), listed on its reference page. Keys
    created from the dashboard today carry no scopes and therefore have full
    access; a key that has scopes but lacks the one an endpoint requires gets
    `403 Forbidden`.


    ## Rate Limiting


    Requests are rate-limited per merchant. Every response includes
    `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`
    headers. Check these headers to know your current limit and remaining quota.
    When the limit is exceeded, the API responds with `429 Too Many Requests`
    and adds a `Retry-After` header.


    ## Pagination


    List endpoints support pagination via `page` and `limit` query parameters.
    Responses include a `pagination` object with `total_pages`, `total_items`,
    and `has_more` fields. Maximum `limit` is 100.
  version: 1.14.2
servers:
  - url: https://api.ya.onl
security: []
tags:
  - name: Orders
    description: Retrieve merchant orders and their details.
  - name: Products
    description: CRUD operations on merchant products.
  - name: Product Variants
    description: Manage variants of a product.
  - name: Product Options
    description: Manage product options.
  - name: Product Option Values
    description: Manage individual option values.
  - name: Product Media
    description: Manage media files attached to product variants.
  - name: Categories
    description: CRUD operations on merchant categories (tags).
  - name: Product Categories
    description: Assign and remove categories from products.
  - name: Catalogs
    description: Retrieve merchant catalogs.
  - name: Webhooks
    description: Manage webhook subscriptions for real-time order events.
  - name: Conversations
    description: Read WhatsApp conversations and their messages.
  - name: Contacts
    description: Retrieve campaign contacts.
  - name: Company
    description: Retrieve the merchant's company profile.
  - name: Templates
    description: >-
      List the WhatsApp templates approved for the merchant, with the variables
      each one expects.
  - name: Template Messages
    description: >-
      Send approved WhatsApp templates to customers and follow their delivery
      status.
paths:
  /v1/template-messages:
    post:
      tags:
        - Template Messages
      summary: Send a template message
      description: >-
        Send an approved WhatsApp template to a customer.


        A send that is taken answers `202` with status `accepted` as soon as the

        sending service has it — the reply carries no delivery outcome. Poll

        `GET /template-messages/{event_id}` for what became of it; no webhook

        reports a send outcome today.


        Idempotent on `event_id`: repeating one returns the state of the first

        request and never produces a second message.


        A `422` names the offending field: a phone outside strict E.164, a
        template

        that is not approved for this merchant in that language, or a missing

        template variable. It also covers the two account-level refusals that no

        payload can fix — an account that is not on the conversational rail this

        API sends from, and an account with no connected WhatsApp sender number
        —

        each carrying its own `type`. Neither is worth a retry.


        An event older than the freshness window, or one whose conversation is

        paused, is not an error either: it answers `200` with status `discarded`

        and the reason in `status_detail`.


        A `402` means the account cannot pay for the message and nothing was
        sent.

        `insufficient_funds` and `wallet_sends_paused` clear as soon as the
        account

        adds funds, and the same `event_id` may be sent again once it has;

        `wallet_currency_mismatch` needs us. Retrying without adding funds
        returns

        the same answer.
      operationId: send_template_message_v1_template_messages_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TemplateMessageRequest'
        required: true
      responses:
        '200':
          description: >-
            No new send was produced: the `event_id` had already been received
            and the stored state is returned, or the event settled without being
            sent
          headers:
            X-RateLimit-Limit:
              description: Maximum number of requests allowed in the current window
              schema:
                type: integer
                example: 100
            X-RateLimit-Remaining:
              description: Number of requests remaining in the current window
              schema:
                type: integer
                example: 87
            X-RateLimit-Reset:
              description: Unix timestamp when the rate limit window resets
              schema:
                type: integer
                example: 1710288000
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateMessageResponse'
        '202':
          description: >-
            Accepted for delivery. The response does not carry the delivery
            outcome — poll `GET /template-messages/{event_id}` for it
          headers:
            X-RateLimit-Limit:
              description: Maximum number of requests allowed in the current window
              schema:
                type: integer
                example: 100
            X-RateLimit-Remaining:
              description: Number of requests remaining in the current window
              schema:
                type: integer
                example: 87
            X-RateLimit-Reset:
              description: Unix timestamp when the rate limit window resets
              schema:
                type: integer
                example: 1710288000
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateMessageResponse'
        '401':
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIErrorResponse'
        '402':
          description: >-
            The account cannot pay for this message. `type` says which:
            `insufficient_funds` (no funds left), `wallet_sends_paused` (sending
            stopped when the funds ran out) or `wallet_currency_mismatch`. The
            first two clear as soon as the account adds funds, and sending
            resumes on its own; the third does not and needs us. Retrying
            without adding funds returns the same answer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIErrorResponse'
        '403':
          description: API key lacks the `outbound:send` scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIErrorResponse'
        '409':
          description: >-
            The `event_id` is bound to a different message, or it aged out of
            the idempotency window. Send the event under a new `event_id`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIErrorResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '429':
          description: Rate limit exceeded
          headers:
            X-RateLimit-Limit:
              description: Maximum number of requests allowed in the current window
              schema:
                type: integer
                example: 100
            X-RateLimit-Remaining:
              description: Number of requests remaining in the current window
              schema:
                type: integer
                example: 87
            X-RateLimit-Reset:
              description: Unix timestamp when the rate limit window resets
              schema:
                type: integer
                example: 1710288000
            Retry-After:
              description: Seconds to wait before making another request
              schema:
                type: integer
                example: 30
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIErrorResponse'
        '502':
          description: The outbound sending service is unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/APIErrorResponse'
      security:
        - APIKeyHeader: []
components:
  schemas:
    TemplateMessageRequest:
      properties:
        event_id:
          type: string
          pattern: ^[A-Za-z0-9._-]{8,128}$
          title: Event Id
          description: >-
            Your identifier for the event. Idempotency key and lookup key for
            the status endpoint; must be stable across retries. The charset is
            bounded because this value travels in the status URL
          examples:
            - CART-8F3A21
        phone:
          type: string
          title: Phone
          description: >-
            Destination phone in strict E.164 (`+` country code, 8-15 digits). A
            number outside the format is rejected with 422 — the country code is
            never inferred
          examples:
            - '+51987654321'
        template:
          $ref: '#/components/schemas/TemplateRef'
          description: Approved template to send, by name and language
        variables:
          additionalProperties:
            type: string
          type: object
          title: Variables
          description: >-
            Template variable values, keyed exactly as `required_variables`
            lists them in `GET /v1/templates`: by position on a positional
            template (`"1"` fills `{{1}}`), by name on a template authored with
            named placeholders (`"name"` fills `{{name}}`). A missing or unknown
            key is a 422
          examples:
            - '1': Juan
              '2': S/ 1.250
            - name: Juan
        occurred_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Occurred At
          description: >-
            ISO 8601 UTC timestamp of the originating event. An event older than
            the freshness window is discarded instead of sent
          examples:
            - '2026-08-11T13:22:05Z'
        metadata:
          anyOf:
            - $ref: '#/components/schemas/TemplateMessageMetadata'
            - type: 'null'
          description: Optional attribution metadata
      type: object
      required:
        - event_id
        - phone
        - template
      title: TemplateMessageRequest
      description: >-
        Public request body for a template send.


        Only `event_id` is shape-checked here. Everything else is validated by
        the

        service in the order the contract declares, so a replay of an
        already-seen

        `event_id` short-circuits before any other field is judged.
    TemplateMessageResponse:
      properties:
        event_id:
          type: string
          title: Event Id
          description: The `event_id` sent in the request
        status:
          $ref: '#/components/schemas/TemplateMessageStatus'
          description: >-
            Current status of the send. A send that was taken answers
            `accepted`; poll the status endpoint for what became of it. `sent`,
            `delivered`, `read` and `failed` come from delivery receipts, and
            `discarded` means it was never sent
        status_detail:
          anyOf:
            - type: string
            - type: 'null'
          title: Status Detail
          description: >-
            Reason behind a `failed` or `discarded` status, or the
            `delivery_not_observable` note on an `accepted` one. **Not a closed
            set, and may be absent.** `stale`, `paused`, `account_not_eligible`,
            `no_sender_number`, `send_failed`, `send_cancelled` and
            `delivery_not_observable` are ours and stable — the last one means
            the send went to a store this deployment cannot read, so the status
            will not settle here; when the provider refused the delivery it
            carries `provider_error_<code>` with the provider's own numeric code
            (for example `provider_error_131049`), and that catalogue changes
            without notice. Branch on `status`, log this, and treat an
            unrecognised or missing value as a generic failure
          examples:
            - stale
            - provider_error_131049
      type: object
      required:
        - event_id
        - status
      title: TemplateMessageResponse
    APIErrorResponse:
      properties:
        type:
          type: string
          title: Type
          description: Error type identifier, e.g. 'UnauthorizedError'
        detail:
          type: string
          title: Detail
          description: Human-readable error message
      type: object
      required:
        - type
        - detail
      title: APIErrorResponse
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    TemplateRef:
      properties:
        name:
          type: string
          maxLength: 512
          minLength: 1
          title: Name
          description: Template name exactly as approved in Meta
          examples:
            - cart_recovery
        language:
          type: string
          maxLength: 15
          minLength: 2
          title: Language
          description: Template language code. `es` and `es_PE` are not the same template
          examples:
            - es
      type: object
      required:
        - name
        - language
      title: TemplateRef
    TemplateMessageMetadata:
      properties:
        use_case:
          anyOf:
            - type: string
              maxLength: 120
            - type: 'null'
          title: Use Case
          description: Free-form label used to attribute the send to a use case
          examples:
            - cart_abandonment
      type: object
      title: TemplateMessageMetadata
    TemplateMessageStatus:
      type: string
      enum:
        - accepted
        - in_progress
        - sent
        - delivered
        - read
        - failed
        - discarded
      title: TemplateMessageStatus
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
  securitySchemes:
    APIKeyHeader:
      type: apiKey
      description: Merchant API key for authentication
      in: header
      name: X-API-Key

````

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