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

# Create Template

> Create a WhatsApp message template for an application.

## What it does

Creates a new WhatsApp message template and submits it to Meta for approval. On success the Meta ID is returned and the template is written through to the local DB (best-effort; the request does not fail even if the DB write fails). A newly created template's status is usually `PENDING`.

***

## Endpoint

```text theme={null}
POST /public/api/v1/wa/{app_id}/templates
```

There are no query parameters.

### Headers

<ParamField header="Authorization" type="string" required>
  Secret key in `Bearer sk_...` format. See [Secret Key authentication](/essentials/authentication#secret-key).
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Always `application/json`.
</ParamField>

### Path parameters

<ParamField path="app_id" type="string" required>
  Application ID. Must belong to the organization (otherwise `404`).
</ParamField>

***

## Request Body

### Top-level fields

<ParamField body="name" type="string" required>
  Template name (max 512 chars). Pattern: `^[a-z][a-z0-9_]*$` (must start with a lowercase letter; lowercase letters, digits, and underscores only). Must be unique.
</ParamField>

<ParamField body="language" type="string" required>
  Language code (BCP-47), e.g. `tr`, `en`, `en_US`.
</ParamField>

<ParamField body="category" type="string" required>
  See [Template Category](/public-api-reference/template/reference/enums#template-category). Determines the component rules (see the Category rules section below).
</ParamField>

<ParamField body="header" type="object | null">
  Header component. See the header object below.
</ParamField>

<ParamField body="body" type="object" required>
  Body component. See the body object below.
</ParamField>

<ParamField body="footer" type="object | null">
  Footer component. See the footer object below.
</ParamField>

<ParamField body="buttons" type="array | null">
  List of buttons (max 10). See the buttons object below.
</ParamField>

<ParamField body="allowCategoryChange" type="boolean | null">
  If `true`, Meta may automatically re-classify the template's category.
</ParamField>

<ParamField body="messageSendTtlSeconds" type="integer | null">
  Message time-to-live. `UTILITY`: 30–43200, `MARKETING`: 43200–2592000, `AUTHENTICATION`: 30–900 (or `-1`).
</ParamField>

### header object

<ParamField body="format" type="string">
  One of `NONE` (default), `TEXT`, `IMAGE`, `VIDEO`, `DOCUMENT`, `LOCATION`.
</ParamField>

<ParamField body="text" type="string">
  Only for `TEXT` headers (max 60 chars). Cannot start or end with a variable.
</ParamField>

<ParamField body="examples" type="string | string[]">
  Variable example for a `TEXT` header; for media headers, the uploaded media handle.
</ParamField>

### body object

<ParamField body="text" type="string">
  Body text (max 1024 chars); supports `{{1}}`, `{{2}}` variables. Cannot start or end with a variable. Required for `UTILITY`/`MARKETING`.
</ParamField>

<ParamField body="examples" type="string[]">
  Example values for the variables (order matters). The count must equal the number of variables.
</ParamField>

<ParamField body="addSecurityRecommendation" type="boolean">
  `AUTHENTICATION` only; adds the security recommendation text.
</ParamField>

### footer object

<ParamField body="text" type="string">
  Footer text (max 60 chars). Cannot be used in `AUTHENTICATION`.
</ParamField>

<ParamField body="codeExpirationMinutes" type="integer">
  `AUTHENTICATION` only (1–90); the code validity duration.
</ParamField>

### buttons object

Each item in the `buttons` array:

<ParamField body="type" type="string">
  One of `QUICK_REPLY`, `URL`, `PHONE_NUMBER`, `COPY_CODE`, `OTP`.
</ParamField>

<ParamField body="text" type="string">
  Button text (2–25 chars). Required for `URL` / `PHONE_NUMBER` / `QUICK_REPLY`.
</ParamField>

<ParamField body="url" type="string">
  `URL` type target link (`https://...`); can be dynamic using `{{1}}`.
</ParamField>

<ParamField body="phoneNumber" type="string">
  `PHONE_NUMBER` type phone in E.164 format.
</ParamField>

<ParamField body="example" type="string[]">
  Dynamic URL example, or the `COPY_CODE` coupon code (required for `COPY_CODE`).
</ParamField>

<ParamField body="otpType" type="string">
  For `OTP`: one of `COPY_CODE`, `ONE_TAP`, `ZERO_TAP`.
</ParamField>

<ParamField body="autofillText" type="string">
  `ONE_TAP` / `ZERO_TAP` autofill text (max 25 chars).
</ParamField>

<ParamField body="packageName" type="string">
  `ONE_TAP` / `ZERO_TAP` Android package name.
</ParamField>

<ParamField body="signatureHash" type="string">
  `ONE_TAP` / `ZERO_TAP` signature hash.
</ParamField>

<ParamField body="zeroTapTermsAccepted" type="boolean">
  `ZERO_TAP` terms acceptance.
</ParamField>

***

## Category rules

<Note>
  **AUTHENTICATION** — no `header`; no `body.text` (only `addSecurityRecommendation`); no `footer.text` (only `codeExpirationMinutes`); buttons may only be `OTP`.
</Note>

<Note>
  **UTILITY / MARKETING** — `body.text` is required; `addSecurityRecommendation` and `codeExpirationMinutes` cannot be used; `OTP` buttons are not allowed; `COPY_CODE` is allowed in `MARKETING` only.
</Note>

<Note>
  The body/header cannot start or end with a variable, and the number of `examples` must equal the number of variables.
</Note>

***

## Response

The envelope is `{ "data": {...}, "isSuccess": true }` (HTTP 200). The `data` is a lean creation response:

<ResponseField name="isSuccess" type="boolean">
  `true` when the template was created and submitted.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="sourceId" type="string">
      The template's Meta ID.
    </ResponseField>

    <ResponseField name="name" type="string">
      Template name.
    </ResponseField>

    <ResponseField name="language" type="string">
      Language code.
    </ResponseField>

    <ResponseField name="category" type="string">
      See [Template Category](/public-api-reference/template/reference/enums#template-category). May differ if Meta re-classifies the template.
    </ResponseField>

    <ResponseField name="status" type="string">
      Usually `PENDING` (under review). See [Template Status](/public-api-reference/template/reference/enums#template-status).
    </ResponseField>
  </Expandable>
</ResponseField>

<Note>
  The create response does not return `components`, `qualityScore`, `rejectedReason`, `parameterFormat`, or the DB id — use [Get Template Detail](/public-api-reference/template/get-template-detail) or [Get Templates](/public-api-reference/template/get-templates) for those.
</Note>

***

## Examples

<AccordionGroup>
  <Accordion title="Demo 1 — UTILITY (lean)">
    ```text theme={null}
    POST /public/api/v1/wa/{app_id}/templates
    Authorization: Bearer sk_...
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "name": "shipping_update",
      "language": "en",
      "category": "UTILITY",
      "body": { "text": "Hi {{1}}, your order {{2}} is on its way.", "examples": ["John", "#12345"] }
    }
    ```

    ```json theme={null}
    { "data": { "sourceId": "1029384756123", "name": "shipping_update", "language": "en", "category": "UTILITY", "status": "PENDING" }, "isSuccess": true }
    ```
  </Accordion>

  <Accordion title="Demo 2 — UTILITY (full: header + body + footer + buttons)">
    ```text theme={null}
    POST /public/api/v1/wa/{app_id}/templates
    Authorization: Bearer sk_...
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "name": "order_confirmation",
      "language": "en",
      "category": "UTILITY",
      "header": { "format": "TEXT", "text": "Order #{{1}} confirmed", "examples": ["12345"] },
      "body": { "text": "Hello {{1}}, your order {{2}} has been confirmed. Estimated delivery is {{3}} business days.", "examples": ["John", "#12345", "3"] },
      "footer": { "text": "Sent via Connexease" },
      "buttons": [
        { "type": "URL", "text": "Track Order", "url": "https://example.com/track/{{1}}", "example": ["https://example.com/track/abc123"] },
        { "type": "PHONE_NUMBER", "text": "Call Us", "phoneNumber": "+905551234567" }
      ]
    }
    ```

    ```json theme={null}
    { "data": { "sourceId": "1029384756124", "name": "order_confirmation", "language": "en", "category": "UTILITY", "status": "PENDING" }, "isSuccess": true }
    ```
  </Accordion>

  <Accordion title="Demo 3 — MARKETING (coupon / COPY_CODE)">
    ```text theme={null}
    POST /public/api/v1/wa/{app_id}/templates
    Authorization: Bearer sk_...
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "name": "promo_coupon",
      "language": "en",
      "category": "MARKETING",
      "body": { "text": "Hey {{1}}, here is your exclusive coupon. Use it at checkout to save big.", "examples": ["John"] },
      "footer": { "text": "Limited time offer" },
      "buttons": [ { "type": "COPY_CODE", "example": ["SAVE25"] } ]
    }
    ```

    ```json theme={null}
    { "data": { "sourceId": "1029384756125", "name": "promo_coupon", "language": "en", "category": "MARKETING", "status": "PENDING" }, "isSuccess": true }
    ```
  </Accordion>

  <Accordion title="Demo 4 — AUTHENTICATION (OTP)">
    ```text theme={null}
    POST /public/api/v1/wa/{app_id}/templates
    Authorization: Bearer sk_...
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "name": "login_otp",
      "language": "en",
      "category": "AUTHENTICATION",
      "body": { "addSecurityRecommendation": true },
      "footer": { "codeExpirationMinutes": 10 },
      "buttons": [ { "type": "OTP", "otpType": "COPY_CODE", "text": "Copy code" } ]
    }
    ```

    ```json theme={null}
    { "data": { "sourceId": "1029384756126", "name": "login_otp", "language": "en", "category": "AUTHENTICATION", "status": "PENDING" }, "isSuccess": true }
    ```
  </Accordion>

  <Accordion title="Demo 5 — error: same name+language already exists → 409">
    ```text theme={null}
    POST /public/api/v1/wa/{app_id}/templates
    Authorization: Bearer sk_...
    Content-Type: application/json
    ```

    ```json theme={null}
    { "name": "order_confirmation", "language": "en", "category": "UTILITY", "body": { "text": "Hi {{1}}.", "examples": ["John"] } }
    ```

    ```json theme={null}
    { "errors": { "code": "META_017", "group": "CONFLICT", "description": "Template creation failed. Details: ..." }, "isSuccess": false }
    ```
  </Accordion>
</AccordionGroup>

***

## Errors

<ResponseField name="errors" type="object">
  Error details with `code`, `group`, and `description`; `isSuccess` is `false`.
</ResponseField>

### Auth / tenant

| HTTP | Code                      | When                                          |
| ---- | ------------------------- | --------------------------------------------- |
| 401  | `ORGANIZATION_SECRET_010` | Secret key is missing.                        |
| 401  | `ORGANIZATION_SECRET_011` | Secret key is invalid.                        |
| 404  | `APPLICATION_004`         | `app_id` does not belong to the organization. |

### Request validation (before reaching Meta, 422)

| HTTP | When                                                                                                                                                                                                     |
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 422  | Invalid `name` pattern, body starts/ends with a variable, examples count mismatch, category-component rule violation (e.g. header in AUTH), button rule violation, `messageSendTtlSeconds` out of range. |

### Meta-side (`create_template_async`)

| HTTP | Code       | When                                                     |
| ---- | ---------- | -------------------------------------------------------- |
| 401  | `META_018` | Token invalid/expired (190).                             |
| 403  | `META_019` | Permission denied (10 / 200–299).                        |
| 429  | `META_020` | Rate limit (4 / 80007).                                  |
| 400  | `META_021` | Invalid parameter (100, no subcode).                     |
| 409  | `META_017` | Same `name`+`language` already exists (subcode 2388024). |
| 422  | `META_022` | Character limit exceeded (2388040).                      |
| 422  | `META_023` | Header format error (2388047).                           |
| 422  | `META_024` | Body format error (2388072).                             |
| 422  | `META_025` | Footer format error (2388073).                           |
| 422  | `META_026` | Too many variables / text ratio (2388293).               |
| 422  | `META_027` | Variable in first/last position (2388299).               |
| 422  | `META_028` | WABA migration conflict (2388103).                       |
| 422  | `META_029` | Other policy/validation (unknown 2388xxx).               |
| 502  | `META_003` | General/fallback creation error.                         |
