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

# POST /v1/templates

> Create a WhatsApp template and submit it to Meta for approval.

Creates a template on the given WhatsApp channel and submits it to Meta for review. The response includes the template's internal `id` and the `status` it landed in. Until Meta approves the template, you can't use it in [a `template` message](/dev/endpoints/messages-send) — use [`GET /v1/templates`](/dev/endpoints/templates-list) to check when it becomes `APPROVED`.

## Endpoint

```
POST https://api.keebai.com/v1/templates
```

## Required scope

`templates:create`

## Headers

| Header          | Required | Value                    |
| --------------- | -------- | ------------------------ |
| `Authorization` | Yes      | `Bearer kbai_pk_<token>` |
| `Content-Type`  | Yes      | `application/json`       |

## Body

| Field              | Type     | Required | Description                                                               |
| ------------------ | -------- | -------- | ------------------------------------------------------------------------- |
| `channel_id`       | `string` | Yes      | `ObjectId` of the WhatsApp channel where the template is created.         |
| `name`             | `string` | Yes      | Template name. Lowercase letters, numbers, and underscores only. Max 512. |
| `language`         | `string` | Yes      | Language code (`es`, `es_CL`, `en_US`, etc.). Max 10.                     |
| `category`         | `string` | Yes      | `MARKETING`, `UTILITY`, or `AUTHENTICATION`.                              |
| `parameter_format` | `string` | No       | `NAMED` (recommended) or `POSITIONAL`.                                    |
| `components`       | `array`  | Yes      | Template components. Same structure as Meta's.                            |
| `metadata`         | `object` | No       | Free-form metadata attached to the template.                              |

### Structure of `components[]`

| Field                         | Type      | Description                                                                                                                                            |
| ----------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`                        | `string`  | `HEADER`, `BODY`, `FOOTER`, or `BUTTONS`.                                                                                                              |
| `format`                      | `string`  | Only for `HEADER`: `TEXT`, `IMAGE`, `VIDEO`, or `DOCUMENT`.                                                                                            |
| `text`                        | `string`  | Component text. `BODY` supports `{{name}}` variables. Max 1024.                                                                                        |
| `buttons`                     | `array`   | Only for `type: BUTTONS`. Each button has `type` (`QUICK_REPLY`, `URL`, `PHONE_NUMBER`, `FLOW`, `VOICE_CALL`, `COPY_CODE`, `OTP`) and `text` (max 25). |
| `example`                     | `object`  | Variable examples Meta requires when reviewing (`body_text`, `body_text_named_params`, `header_text`, `header_handle`).                                |
| `add_security_recommendation` | `boolean` | Only for authentication templates.                                                                                                                     |
| `code_expiration_minutes`     | `number`  | Only for authentication templates.                                                                                                                     |

## Example request

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.keebai.com/v1/templates \
    -H "Authorization: Bearer kbai_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "channel_id": "65f3a1b2c3d4e5f6a7b8c9d0",
      "name": "welcome_v2",
      "language": "es_CL",
      "category": "UTILITY",
      "parameter_format": "NAMED",
      "components": [
        {
          "type": "BODY",
          "text": "Hola {{nombre}}, tu pedido por {{monto}} está confirmado.",
          "example": {
            "body_text_named_params": [
              { "param_name": "nombre", "example": "Juan" },
              { "param_name": "monto", "example": "$15.000" }
            ]
          }
        }
      ]
    }'
  ```

  ```js JavaScript theme={null}
  const resp = await fetch("https://api.keebai.com/v1/templates", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.KEEBAI_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      channel_id: "65f3a1b2c3d4e5f6a7b8c9d0",
      name: "welcome_v2",
      language: "es_CL",
      category: "UTILITY",
      parameter_format: "NAMED",
      components: [
        {
          type: "BODY",
          text: "Hola {{nombre}}, tu pedido por {{monto}} está confirmado.",
          example: {
            body_text_named_params: [
              { param_name: "nombre", example: "Juan" },
              { param_name: "monto", example: "$15.000" },
            ],
          },
        },
      ],
    }),
  });
  const template = await resp.json();
  ```

  ```python Python theme={null}
  import os, requests

  resp = requests.post(
      "https://api.keebai.com/v1/templates",
      headers={
          "Authorization": f"Bearer {os.environ['KEEBAI_API_TOKEN']}",
          "Content-Type": "application/json",
      },
      json={
          "channel_id": "65f3a1b2c3d4e5f6a7b8c9d0",
          "name": "welcome_v2",
          "language": "es_CL",
          "category": "UTILITY",
          "parameter_format": "NAMED",
          "components": [
              {
                  "type": "BODY",
                  "text": "Hola {{nombre}}, tu pedido por {{monto}} está confirmado.",
                  "example": {
                      "body_text_named_params": [
                          {"param_name": "nombre", "example": "Juan"},
                          {"param_name": "monto", "example": "$15.000"},
                      ]
                  },
              }
          ],
      },
      timeout=20,
  )
  resp.raise_for_status()
  template = resp.json()
  ```
</CodeGroup>

## Response

### 201 Created

```json theme={null}
{
  "id": "65f3a1b2c3d4e5f6a7b8c9d2",
  "name": "welcome_v2",
  "language": "es_CL",
  "status": "PENDING",
  "category": "UTILITY",
  "parameter_format": "NAMED",
  "variables": ["nombre", "monto"]
}
```

| Field              | Type       | Description                                                                      |
| ------------------ | ---------- | -------------------------------------------------------------------------------- |
| `id`               | `string`   | Internal `ObjectId` of the newly created template.                               |
| `name`             | `string`   | Name as it was registered.                                                       |
| `language`         | `string`   | Language code.                                                                   |
| `status`           | `string`   | Initial status reported by Meta (`PENDING` or `IN_REVIEW` right after creation). |
| `category`         | `string`   | Assigned category.                                                               |
| `parameter_format` | `string`   | `NAMED` or `POSITIONAL`.                                                         |
| `variables`        | `string[]` | Named variables detected in the body.                                            |

### 400 / 401 / 403 / 404 / 409 / 429

Standard errors. Common cases:

* `400 BAD_REQUEST`: body validation (invalid characters in name, malformed components, missing `example` when Meta requires it).
* `403 FORBIDDEN` with `code: INSUFFICIENT_SCOPE`: the PAT does not have `templates:create`.
* `404 NOT_FOUND`: the `channel_id` does not belong to your tenant or does not exist.
* `409 CONFLICT`: a template with the same `name` + `language` already exists on that channel.
* `502 UPSTREAM_ERROR`: Meta rejected the creation. The `details` field explains why.

<Tip>
  After creating the template you have to wait for Meta to approve it before sending. Poll [`GET /v1/templates`](/dev/endpoints/templates-list) filtering by `status` until it shows up as `APPROVED`. Approval usually takes minutes, but can take longer for sensitive categories like `MARKETING`.
</Tip>


## OpenAPI

````yaml POST /v1/templates
openapi: 3.0.0
info:
  title: Keebai Public API
  description: Superficie pública REST autenticada con Personal Access Tokens (PAT).
  version: 1.0.0
  contact: {}
servers:
  - url: https://api.keebai.com
    description: Production
security: []
tags: []
paths:
  /v1/templates:
    post:
      tags:
        - templates
      summary: Crear un template de WhatsApp y enviarlo a Meta para aprobación
      operationId: PublicTemplatesController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTemplateDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicTemplateDto'
      security:
        - PAT: []
components:
  schemas:
    CreateTemplateDto:
      type: object
      properties:
        channel_id:
          type: string
          description: ObjectId del canal WhatsApp donde se crea el template.
        name:
          type: string
          maxLength: 512
          description: Nombre del template. Solo minúsculas, números y guiones bajos.
          example: welcome_v2
        language:
          type: string
          maxLength: 10
          example: es_CL
        category:
          type: string
          enum:
            - MARKETING
            - UTILITY
            - AUTHENTICATION
        parameter_format:
          type: string
          enum:
            - NAMED
            - POSITIONAL
        components:
          type: array
          items:
            $ref: '#/components/schemas/TemplateComponentDto'
        metadata:
          type: object
          additionalProperties: true
      required:
        - channel_id
        - name
        - language
        - category
        - components
    PublicTemplateDto:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        language:
          type: string
        status:
          type: string
        category:
          type: string
        parameter_format:
          type: string
        variables:
          type: array
          items:
            type: string
      required:
        - id
        - name
        - language
        - status
        - category
        - variables
    TemplateComponentDto:
      type: object
      properties:
        type:
          type: string
          enum:
            - HEADER
            - BODY
            - FOOTER
            - BUTTONS
        format:
          type: string
          enum:
            - TEXT
            - IMAGE
            - VIDEO
            - DOCUMENT
        text:
          type: string
          maxLength: 1024
        buttons:
          type: array
          items:
            $ref: '#/components/schemas/TemplateButtonDto'
        example:
          $ref: '#/components/schemas/TemplateExampleDto'
        add_security_recommendation:
          type: boolean
        code_expiration_minutes:
          type: number
      required:
        - type
    TemplateButtonDto:
      type: object
      properties:
        type:
          type: string
          enum:
            - QUICK_REPLY
            - URL
            - PHONE_NUMBER
            - FLOW
            - VOICE_CALL
            - COPY_CODE
            - OTP
        text:
          type: string
          maxLength: 25
        url:
          type: string
        phone_number:
          type: string
        example:
          type: array
          items:
            type: string
        flow_id:
          type: string
        flow_action:
          type: string
          enum:
            - navigate
            - data_exchange
        coupon_code:
          type: string
        otp_type:
          type: string
          enum:
            - COPY_CODE
            - ONE_TAP
            - ZERO_TAP
        autofill_text:
          type: string
        package_name:
          type: string
        signature_hash:
          type: string
      required:
        - type
        - text
    TemplateExampleDto:
      type: object
      properties:
        header_text:
          type: array
          items:
            type: string
        header_text_named_params:
          type: array
          items:
            $ref: '#/components/schemas/NamedParamDto'
        header_handle:
          type: array
          items:
            type: string
        body_text:
          type: array
          items:
            type: string
        body_text_named_params:
          type: array
          items:
            $ref: '#/components/schemas/NamedParamDto'
    NamedParamDto:
      type: object
      properties:
        param_name:
          type: string
          example: nombre
        example:
          type: string
          example: Juan
      required:
        - param_name
        - example
  securitySchemes:
    PAT:
      scheme: bearer
      bearerFormat: kbai_pk_<hex>
      type: http
      description: >-
        Personal Access Token con prefijo `kbai_pk_`. Generar desde el portal
        con permiso `developer.manage_tokens`.

````