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

# Submit a template to WhatsApp

> Submit a draft template to Meta for approval, naming the WhatsApp number by its address (`from`).



## OpenAPI

````yaml /api-reference/v2/openapi.json post /v2/templates/{templateId}/submit
openapi: 3.0.3
info:
  title: Zavu API v2
  version: 2.0.0
  description: >-
    The second version of the Zavu API. It runs on the same engine as /v1: the
    same API keys, the same per-project rate limit, the same sending limits,
    prices and reviews. /v1 keeps working unchanged, and you can mix calls to
    both.


    **Addresses, not senders.** /v2 has no sender object. Where a message leaves
    from is named the way the recipient sees it: an E.164 number, an email
    address, `@botname` for Telegram, `@username` for Instagram, the Facebook
    Page id for Messenger, and the literal `sms_oneway` for one-way SMS (sent
    from shared numbers, never a number of your own). `GET /v2/channels` lists
    one entry per (address, type); a number that carries WhatsApp and SMS is two
    entries with the same address.


    **What else changes.** No `Zavu-Sender` header (`from` or `conversationId`
    instead); every send reports how it was routed; one project webhook carries
    every event; every error is `{code, message, details}` at the root.


    **Wrong method.** A path under `/v2/messages`, `/v2/conversations`,
    `/v2/contacts`, `/v2/channels`, `/v2/templates`, `/v2/campaigns`,
    `/v2/agents`, `/v2/webhook` or `/v2/email-domains` called with a method it
    does not take answers `405 method_not_allowed`, with `details.allowed` and
    an `Allow` header.


    **Money.** Every amount is USD, rounded to 6 decimals.
servers:
  - url: https://api.zavu.dev
security:
  - bearerAuth: []
tags:
  - name: Messages
    description: Send, list and read messages.
  - name: Conversations
    description: Inbox threads, one per contact across channels.
  - name: Contacts
    description: Contacts and the identifiers they are reached at.
  - name: Channels
    description: 'Where the project can send from: one entry per (address, type).'
  - name: Templates
    description: WhatsApp message templates.
  - name: Campaigns
    description: One message to many recipients.
  - name: Webhook
    description: The one project webhook for every event.
  - name: Agents
    description: AI agents and the addresses they answer on.
  - name: Numbers
    description: Search and buy phone numbers.
  - name: Email Domains
    description: Domains to send email from.
  - name: Functions
    description: Configure Zavu Functions.
paths:
  /v2/templates/{templateId}/submit:
    post:
      tags:
        - Templates
      summary: Submit a template to WhatsApp
      description: >-
        Submit a draft template to Meta for approval, naming the WhatsApp number
        by its address (`from`).
      operationId: submitTemplateV2
      parameters:
        - name: templateId
          in: path
          required: true
          description: ''
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TemplateSubmitRequest'
      responses:
        '202':
          description: Submitted; status becomes `pending`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Template'
        '400':
          description: >-
            Not a draft, or `no_whatsapp`: the `from` address exists but has no
            WhatsApp Business Account (`details.types` lists what it has).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: >-
            Template not found, or no channel of this project has the `from`
            address.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            `ambiguous_from`: two of your senders have a `whatsapp` entry with
            this address.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - bearerAuth: []
components:
  schemas:
    TemplateSubmitRequest:
      type: object
      required:
        - from
      properties:
        from:
          type: string
          description: >-
            The WhatsApp number whose Business Account receives the template: an
            address with a `whatsapp` entry in `GET /v2/channels`.
        category:
          $ref: '#/components/schemas/WhatsAppCategory'
    Template:
      type: object
      required:
        - id
        - name
        - language
        - body
        - category
      properties:
        id:
          type: string
        name:
          type: string
          description: >-
            Template name. For WhatsApp, must match the approved template name
            in Meta.
          example: order_confirmation
        language:
          type: string
          description: Language code.
          example: en
        body:
          type: string
          description: >-
            Default template body with variables: positional ({{1}}, {{2}}) or
            named ({{customer_name}}, {{contact.first_name}}). Templates created
            in Zavu are submitted to Meta as positional; templates imported from
            a WhatsApp Business Account keep their original format (named or
            positional). Used when no channel-specific body is set.
          example: Hi {{1}}, your order {{2}} has shipped.
        smsBody:
          type: string
          description: >-
            Channel-specific body for SMS messages. Falls back to `body` if not
            set.
        telegramBody:
          type: string
          description: >-
            Channel-specific body for Telegram messages. Falls back to `body` if
            not set.
        instagramBody:
          type: string
          description: >-
            Channel-specific body for Instagram messages. Falls back to `body`
            if not set.
        category:
          allOf:
            - $ref: '#/components/schemas/WhatsAppCategory'
          description: >-
            The category the template is billed under. For a template linked to
            WhatsApp this is the category **Meta currently assigns**, not the
            one it was submitted with: Meta can recategorize a template at
            approval or at any point afterwards, and that changes what the
            message costs.


            A recategorization reaches this field through the
            `template.status_changed` webhook, which carries `data.category`,
            and through `POST /v1/templates/sync`, which reconciles it against
            Meta. Read one of those rather than assuming the category you sent
            is the one in force.


            For a template that was never submitted to WhatsApp, this is the
            category stored locally, defaulting to `UTILITY`.
        status:
          type: string
          enum:
            - draft
            - pending
            - approved
            - rejected
          default: draft
        variables:
          type: array
          items:
            type: string
          description: List of variable names for documentation.
        headerType:
          type: string
          description: Type of header (text, image, video, document).
        headerContent:
          type: string
          description: Header content (text or media URL).
        footer:
          type: string
          description: Footer text for the template.
        buttons:
          type: array
          description: Template buttons.
          items:
            type: object
            properties:
              type:
                type: string
                enum:
                  - quick_reply
                  - url
                  - phone
                  - otp
                  - request_contact_info
              text:
                type: string
              url:
                type: string
              example:
                type: string
                description: >-
                  Sample value used to substitute `{{1}}` in the URL when
                  submitting the template to Meta for review. Only present for
                  dynamic URL buttons.
              phoneNumber:
                type: string
              otpType:
                type: string
                enum:
                  - COPY_CODE
                  - ONE_TAP
                description: OTP button type. Required when type is 'otp'.
              packageName:
                type: string
                description: Android package name. Required for ONE_TAP buttons.
              signatureHash:
                type: string
                description: Android app signature hash. Required for ONE_TAP buttons.
        addSecurityRecommendation:
          type: boolean
          description: >-
            Add 'Do not share this code' disclaimer. Only for AUTHENTICATION
            templates.
        codeExpirationMinutes:
          type: integer
          minimum: 1
          maximum: 90
          description: Code expiration time in minutes. Only for AUTHENTICATION templates.
        whatsapp:
          type: object
          description: WhatsApp-specific template information.
          properties:
            templateName:
              type: string
              description: WhatsApp template name.
            namespace:
              type: string
              description: WhatsApp Business Account namespace.
            status:
              type: string
              description: WhatsApp approval status.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    Error:
      type: object
      required:
        - code
        - message
      description: >-
        Every v2 error has this shape at the root. There is no `{error: {...}}`
        wrapper anywhere in v2. A missing resource is `404 not_found` with the
        message `<Resource> not found`; an address the project does not have is
        `404 not_found`, `None of this project's channels has the address
        <address>.`, with the address in `details` under the name the request
        used (`from` or `address`). A `message` is written for the caller and
        never carries a stack trace or a source path. A body that is not JSON is
        `400 invalid_json`, with `details.errors` (`path: []`, the parse error
        in `message`).
      properties:
        code:
          type: string
          example: invalid_request
        message:
          type: string
        details:
          type: object
          additionalProperties: true
          description: >-
            Machine-readable context. Keys that mean the same thing everywhere:

            - `errors`: present on every `400 invalid_request` for an invalid
            body or query parameter, `[{path, message, ...}]`, `path` naming the
            field (`["to"]`, `["contacts", 2, "recipient"]`, `["limit"]`).

            - `recommendation`: one sentence saying what to do next.

            - `dashboardUrl`: the dashboard screen that fixes it.

            - `connect`: on `409 no_channel`, the channels this project can
            connect to reach the recipient, each `{type, request, note,
            available, dashboardUrl?}`; `request` is the exact call (`POST
            /v2/channels` with its body, or `POST /v2/numbers`), or null for a
            channel connected from the dashboard (WhatsApp, Instagram,
            Messenger). `available: false` says why in `note` (one-way SMS
            closed until business verification is approved).

            - `address` / `from`: the address the error is about, under the name
            the request used for it.
          properties:
            errors:
              type: array
              items:
                type: object
                required:
                  - path
                  - message
                properties:
                  path:
                    type: array
                    items:
                      oneOf:
                        - type: string
                        - type: integer
                  message:
                    type: string
                  code:
                    type: string
                additionalProperties: true
    WhatsAppCategory:
      type: string
      description: WhatsApp template category.
      enum:
        - UTILITY
        - MARKETING
        - AUTHENTICATION
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````

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