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

# Send a template

> Queue an APPROVED template to 1-100 recipients.

**202 acknowledges acceptance, not delivery** — the returned broadcast's
counts all start at zero and move as the worker fans the batch out. Poll
`GET /v1/broadcasts/{id}` for progress and
`/v1/broadcasts/{id}/recipients` for per-recipient outcomes.

Address each recipient by `contact_id` or `phone`. **A `phone` that matches
no contact creates one**, in the org's initial funnel stage — sending to a
hundred unknown numbers grows the CRM by up to a hundred contacts. The same
person named twice (once by id, once by phone) is queued and charged once.

`GET /v1/templates/{id}/send-requirements` lists exactly what this template
needs filled. Everything it names is validated here **before** the wallet is
touched or Meta is called, so a refusal costs nothing: 400 an unusable or
ambiguous `channel_id`, 402 a batch the WhatsApp balance can't cover, 404 an
unknown template or recipient, 409 a template that is not APPROVED or a
batch past what is left of Meta's rolling 24h messaging window (the whole
request is refused rather than silently truncated), 422 anything missing,
unknown, or unfetchable in the parameters.

Pass `Idempotency-Key` to make a retry safe: the same key returns the
original broadcast instead of queuing a second one, for 24h.



## OpenAPI

````yaml /api-reference/openapi.json post /v1/templates/{template_id}/send
openapi: 3.1.0
info:
  title: Ciarem API
  version: v1
  description: >
    The Ciarem public API. Authenticate every request with an org-bound API key

    (created in Settings → API keys; Scale plan or free trial) sent as a bearer
    token:

    `Authorization: Bearer oak_…`.


    Compatibility: this is an early, fast-moving API. Most changes are

    additive (new endpoints, new optional params, new response fields, new

    enum/string values) — clients must tolerate unknown fields and unknown

    string values. Every contract change is listed in the changelog at

    help.ciarem.ai.
servers:
  - url: https://api.ciarem.ai
security:
  - apiKey: []
tags:
  - name: Account
    description: The organization behind the API key.
  - name: Contacts
    description: People in the CRM — create, read, update, delete.
  - name: CRM
    description: 'The shape of the CRM: custom properties and funnel stages.'
  - name: Conversations
    description: Read the message history of a conversation.
  - name: WhatsApp numbers
    description: The connected numbers a send can go out from.
  - name: WhatsApp templates
    description: Message templates, and what each one needs filled before it can be sent.
  - name: Broadcasts
    description: Send a template, and follow what happened to every recipient.
paths:
  /v1/templates/{template_id}/send:
    post:
      tags:
        - Broadcasts
      summary: Send a template
      description: >-
        Queue an APPROVED template to 1-100 recipients.


        **202 acknowledges acceptance, not delivery** — the returned broadcast's

        counts all start at zero and move as the worker fans the batch out. Poll

        `GET /v1/broadcasts/{id}` for progress and

        `/v1/broadcasts/{id}/recipients` for per-recipient outcomes.


        Address each recipient by `contact_id` or `phone`. **A `phone` that
        matches

        no contact creates one**, in the org's initial funnel stage — sending to
        a

        hundred unknown numbers grows the CRM by up to a hundred contacts. The
        same

        person named twice (once by id, once by phone) is queued and charged
        once.


        `GET /v1/templates/{id}/send-requirements` lists exactly what this
        template

        needs filled. Everything it names is validated here **before** the
        wallet is

        touched or Meta is called, so a refusal costs nothing: 400 an unusable
        or

        ambiguous `channel_id`, 402 a batch the WhatsApp balance can't cover,
        404 an

        unknown template or recipient, 409 a template that is not APPROVED or a

        batch past what is left of Meta's rolling 24h messaging window (the
        whole

        request is refused rather than silently truncated), 422 anything
        missing,

        unknown, or unfetchable in the parameters.


        Pass `Idempotency-Key` to make a retry safe: the same key returns the

        original broadcast instead of queuing a second one, for 24h.
      operationId: send_endpoint_v1_templates__template_id__send_post
      parameters:
        - name: template_id
          in: path
          required: true
          schema:
            type: string
            title: Template Id
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: Idempotency-Key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicSendRequest'
      responses:
        '202':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicBroadcast'
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
        '401':
          description: >-
            No usable credential: `missing_bearer_token` (no `Authorization:
            Bearer`) or `invalid_api_key` (unknown or revoked key).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '403':
          description: >-
            The credential is real but not allowed here: `api_key_required` (a
            session token rather than an `oak_` key), `scale_plan_required` (a
            live plan below Scale — upgrade to Scale), `trial_expired` (the free
            trial ended without a card — subscribe to keep the key working), or
            `subscription_required` (the subscription is canceled or suspended).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '429':
          description: >-
            Over the organization's request budget (`rate_limited`).
            `Retry-After` says how many seconds to wait.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
          headers:
            Retry-After:
              description: Seconds until the window resets.
              schema:
                type: integer
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
components:
  schemas:
    PublicSendRequest:
      properties:
        channel_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Channel Id
        recipients:
          anyOf:
            - $ref: '#/components/schemas/PublicSendRecipient'
            - items:
                $ref: '#/components/schemas/PublicSendRecipient'
              type: array
          title: Recipients
      additionalProperties: false
      type: object
      required:
        - recipients
      title: PublicSendRequest
      description: >-
        Send an APPROVED template to one recipient or a list of them.


        `recipients` accepts a single object or an array with identical
        semantics —

        sending to one person should not require wrapping it — and the response
        is

        a `PublicBroadcast` either way.
    PublicBroadcast:
      properties:
        id:
          type: string
          title: Id
        template_id:
          type: string
          title: Template Id
        template_name:
          type: string
          title: Template Name
        template_language:
          type: string
          title: Template Language
        channel_id:
          type: string
          title: Channel Id
        status:
          $ref: '#/components/schemas/BroadcastDisplayStatus'
        requested:
          type: integer
          title: Requested
        sent:
          type: integer
          title: Sent
        failed:
          type: integer
          title: Failed
        pending:
          type: integer
          title: Pending
        origin:
          type: string
          title: Origin
        created_at:
          type: string
          format: date-time
          title: Created At
      type: object
      required:
        - id
        - template_id
        - template_name
        - template_language
        - channel_id
        - status
        - requested
        - sent
        - failed
        - pending
        - origin
        - created_at
      title: PublicBroadcast
      description: >-
        A queued template send. Counts are derived from the messages it
        produced,

        so they move as the worker fans the batch out; they always sum to

        `requested`.
    PublicError:
      description: An error whose `detail` is a stable, machine-readable code.
      properties:
        detail:
          title: Detail
          type: string
      required:
        - detail
      title: PublicError
      type: object
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    PublicSendRecipient:
      properties:
        contact_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Contact Id
        phone:
          anyOf:
            - type: string
              pattern: ^\+[1-9]\d{1,14}$
            - type: 'null'
          title: Phone
        variables:
          additionalProperties:
            type: string
          type: object
          title: Variables
        coupon_code:
          anyOf:
            - type: string
            - type: 'null'
          title: Coupon Code
        url_suffixes:
          patternProperties:
            ^\d+$:
              type: string
          type: object
          title: Url Suffixes
        lto_expiration:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Lto Expiration
        catalog_records:
          patternProperties:
            ^[a-z][a-z0-9_]*\.\d+$:
              type: string
          type: object
          title: Catalog Records
        header_media_url:
          anyOf:
            - type: string
            - type: 'null'
          title: Header Media Url
        card_media_urls:
          patternProperties:
            ^\d+$:
              type: string
          type: object
          title: Card Media Urls
      additionalProperties: false
      type: object
      title: PublicSendRecipient
      description: |-
        One person to send to, plus what this template needs filled *for them*.

        Exactly one of `contact_id` / `phone`. A `phone` that matches no contact
        creates one (see the endpoint docstring) — the honest consequence of
        addressing a CRM-backed product by phone number.

        `variables` is keyed by the **authored token** — `"1"` for a positional
        template, `"discount_code"` or `"contact.first_name"` for a named one.
        Positional-vs-named is a property of the template and mixing the two is
        fatal at Meta, so this is deliberately not an ordered array.
        `GET /v1/templates/{id}/send-requirements` lists the exact keys.
    BroadcastDisplayStatus:
      type: string
      enum:
        - in_progress
        - completed
        - stopped_low_balance
        - cancelled
      title: BroadcastDisplayStatus
      description: >-
        Derived campaign status shown in the UI. `stopped_low_balance` and

        `cancelled` mirror the stored state; the other two are computed from the

        counts (no stored value — chunked async means no single worker knows it
        sent

        the last recipient).
    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
  headers:
    X-RateLimit-Limit:
      description: Requests allowed per window, shared across the org's keys.
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests left in the current window.
      schema:
        type: integer
    X-RateLimit-Reset:
      description: When the window resets, as a Unix timestamp in seconds.
      schema:
        type: integer
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: 'An org API key: `Authorization: Bearer oak_…`.'

````