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

# Templates

> Create, submit and track message templates.

A template is pre-approved content. It is the only way to message someone outside the 24-hour customer-service window, so most production traffic is templates.

Templates belong to your workspace and mode — there is no `accountId` query parameter. LazyApp submits to Meta on the connected WABA for that workspace.

## Quick start

```bash theme={null}
# List approved and pending templates
curl "https://lazyapp.in/v1/templates?status=approved" \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"

# Create and submit to Meta (default)
curl https://lazyapp.in/v1/templates \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_shipped",
    "language": "en",
    "category": "UTILITY",
    "body_text": "Hi {{1}}, order {{2}} ships today and arrives {{3}}.",
    "body_examples": ["Asha", "1042", "Thursday"],
    "footer_text": "Reply STOP to opt out",
    "submit": true
  }'
```

## List templates

[`GET /v1/templates`](https://docs.lazyapp.in/api-reference/templates/list-templates) returns cursor-paginated templates for the workspace.

| Query | Filters |
| - | - |
| `status` | `draft`, `pending`, `approved`, `rejected`, `paused`, `disabled`, … |
| `category` | `UTILITY`, `AUTHENTICATION`, `MARKETING` |
| `limit` | Page size (max 200) |

Response fields include `uuid`, `name`, `language`, `category`, `status`, `body_text`, `header`, `buttons`, `rejection_reason`, `quality_score`, and Meta's `provider_template_id` when assigned.

Retrieve one template by name or uuid: [`GET /v1/templates/{template}`](https://docs.lazyapp.in/api-reference/templates/retrieve-a-template).

## Categories

Meta requires one of three, and it drives pricing:

| Category | For | Notes |
| - | - | - |
| `UTILITY` | Order updates, appointment reminders, receipts | Cheapest. Must relate to an existing transaction |
| `AUTHENTICATION` | One-time passcodes | Use [Verify](https://docs.lazyapp.in/sending/verify.md) rather than building this yourself |
| `MARKETING` | Offers, announcements, re-engagement | Required for [broadcasts](https://docs.lazyapp.in/sending/broadcasts.md). Most expensive |

> [!WARNING]
>
> Meta re-categorises templates it judges to be miscategorised, which changes what you pay.
> A promotional message dressed as a utility template will be moved to marketing, and you
> receive a `template.category_changed` [webhook](https://docs.lazyapp.in/webhooks/events.md).

## Create a template

LazyApp uses a **flat native shape** on `POST /v1/templates`, not Zernio's Meta `components` array on `/whatsapp/templates`:

```bash theme={null}
curl https://lazyapp.in/v1/templates \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_shipped",
    "language": "en",
    "category": "UTILITY",
    "body_text": "Hi {{1}}, order {{2}} ships today and arrives {{3}}.",
    "body_examples": ["Asha", "1042", "Thursday"],
    "footer_text": "Reply STOP to opt out",
    "submit": true
  }'
```

Rules:

* Names: lowercase letters, digits, underscores only (`^[a-z0-9_]+$`), unique per language in the workspace.
* `body_examples`: one realistic string per `{{n}}` placeholder — Meta rejects templates it cannot evaluate.
* `submit`: defaults to `true` (submit immediately). Pass `"submit": false` to keep a local draft.

### Headers and buttons

```json theme={null}
{
  "header": { "format": "TEXT", "text": "Order update" },
  "buttons": [
    { "type": "URL", "text": "Track", "url": "https://acme.example/track/{{1}}" },
    { "type": "QUICK_REPLY", "text": "Talk to support" }
  ]
}
```

`header.format` is `NONE`, `TEXT`, `IMAGE`, `VIDEO`, `GIF`, `DOCUMENT`, `LOCATION` or `PRODUCT`. Button types: `QUICK_REPLY`, `URL`, `PHONE_NUMBER`, `OTP`, `FLOW`, `COPY_CODE`, `CATALOG`, `MPM`, `SPM` and `VOICE_CALL`. URL buttons must be `https://`.

Media headers need a sample for Meta's review: upload it with [`POST /v1/templates/media`](https://docs.lazyapp.in/api-reference/templates/upload-a-header-sample) and pass the handle as `header.example_handle`. Carousels, GIF headers, call buttons, call permission requests, delivery time limits and library templates are on [Template formats](https://docs.lazyapp.in/sending/template-formats.md).

Authentication templates can pass:

```json theme={null}
{
  "authentication": {
    "add_security_recommendation": true,
    "code_expiration_minutes": 10
  }
}
```

### Meta-shaped create (proxy)

If you already have Kapso/Cloud API payloads, create via the proxy with Meta's `components` envelope:

```bash theme={null}
curl -X POST "https://lazyapp.in/meta/whatsapp/v21.0/{waba_id}/message_templates" \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_confirmation",
    "language": "en",
    "category": "UTILITY",
    "components": [
      { "type": "BODY", "text": "Hi {{1}}, your order {{2}} has been confirmed!" }
    ]
  }'
```

See [Migrating from Cloud API](https://docs.lazyapp.in/migrating/from-cloud-api.md).

## Template statuses

Only `approved` templates can be sent. LazyApp stores lowercase statuses mapped from Meta:

| `status` | Meaning |
| - | - |
| `draft` | Saved in LazyApp, not submitted |
| `submitting` | In flight to Meta |
| `pending` | Under Meta review (includes Meta `IN_APPEAL`) |
| `approved` | Sendable |
| `rejected` | Declined — read `rejection_reason`, fix, resubmit (often under a new name) |
| `paused` | Paused on quality grounds — temporarily unsendable |
| `disabled` | Disabled by Meta — not sendable |
| `archived` | Archived at Meta |
| `deleted` | Deleted, here or in WhatsApp Manager |

> [!NOTE]
>
> Meta review usually takes minutes but can take up to 24 hours. Do not poll the list in a tight loop — subscribe to template [webhooks](https://docs.lazyapp.in/webhooks/events.md).

### Webhooks

| Event | When |
| - | - |
| `template.pending` | Submitted to Meta |
| `template.approved` | Approved and sendable |
| `template.rejected` | Declined (`rejection_reason`) |
| `template.paused` | Quality pause |
| `template.disabled` | Disabled after repeated problems |
| `template.category_changed` | Meta moved category (pricing impact) |
| `template.deleted` | Deleted in WhatsApp Manager, found on the next sync |

Pausing, categories, groups, comparisons, archiving and copying templates between accounts are on [Managing templates](https://docs.lazyapp.in/sending/template-management.md).

## Starter template sets

Platforms can seed templates when a customer connects via [`template_set` on setup links](https://docs.lazyapp.in/platform/setup-links#starter-templates). Discover valid keys:

```bash theme={null}
curl https://lazyapp.in/v1/templates/sets \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

Returns named packs (for example `real_estate`) with template names and languages — do not hardcode keys across deployments.

## Sync and delete

Pull changes made in Meta Business Manager:

```bash theme={null}
curl -X POST https://lazyapp.in/v1/templates/sync \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

LazyApp also updates statuses from Meta webhooks; sync is for reconciliation.

Delete at Meta and mark deleted locally:

```bash theme={null}
curl -X DELETE https://lazyapp.in/v1/templates/{name_or_uuid} \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

## Sending a template

See [Messages](https://docs.lazyapp.in/sending/messages.md#types), [Broadcasts](https://docs.lazyapp.in/sending/broadcasts.md), or [Events](https://docs.lazyapp.in/sending/events.md) to pick the template for you.

### Carousel templates

Each card of a carousel template needs its own image or video (or, on a product carousel, its own product) when you send it; the review sample is not reused. Add values for variables in the card text, the ending of any card link and, optionally, a payload for each quick reply. Pass them per card in `template.features.cards`, in card order:

```json theme={null}
{
  "type": "template",
  "to": "+919812345670",
  "template": {
    "name": "spring_cards",
    "language": "en_US",
    "components": [
      { "type": "body", "parameters": [{ "type": "text", "text": "Asha" }] }
    ],
    "features": {
      "cards": [
        { "header_media": "https://acme.example/aloe.jpg", "body": ["Aloe"], "buttons": { "1": "aloe" } },
        { "header_media": "1558081531584829", "body": ["Crassula"], "buttons": { "1": "crassula" }, "quick_replies": { "0": "more-crassulas" } }
      ]
    }
  }
}
```

| Card field | Meaning |
| - | - |
| `header_media` | Media cards: a public link or an uploaded media id for the card's image or video. Required |
| `product_retailer_id` | Product cards: the product's Content ID in Commerce Manager. Required. `catalog_id` is optional and defaults to the number's catalog |
| `body` | Values for the card text's variables, in order (or keyed by name on named templates) |
| `buttons` | Keyed by button index: the ending of a URL button's link |
| `quick_replies` | Keyed by button index: an optional payload for a quick reply (it comes back on the reply webhook) |

LazyApp builds Meta's `carousel` component from these and refuses the send with a field-level `422` when a card is missing its media, product or values. A media carousel takes exactly as many cards as it was approved with; a product carousel can show 1 to 10 products. See [Template formats](https://docs.lazyapp.in/sending/template-formats.md#sending-these-formats) for every `features` key.

[Broadcasts](https://docs.lazyapp.in/sending/broadcasts.md) take the same inputs in `variable_map.features.cards`, and the campaign form in the console asks for each card.

> [!NOTE]
>
> `template.carousel` (and `variable_map.carousel` on broadcasts) is an older alias and still accepted: each entry takes `media`, `body` and `buttons` keyed by index, where `buttons` holds link endings and quick-reply payloads alike. When both are sent, `features.cards` wins. Use `features.cards` in new code.

## What we skip (Zernio-style)

| Zernio | LazyApp |
| - | - |
| `GET /whatsapp/templates?accountId=` | `GET /v1/templates` (workspace scoped) |
| Meta `components` on native create | Flat `body_text`, `header`, `buttons` on `/v1/templates` |
| Template Library API (`library_template_name`, `template-library` lookup) | [`GET /v1/templates/library`](https://docs.lazyapp.in/api-reference/templates/search-the-template-library), then `POST /v1/templates` with `library_template_name` |
| `whatsapp.template.status_updated` event name | `template.approved`, `template.rejected`, etc. |

## Related

* [Messages](https://docs.lazyapp.in/sending/messages.md) — send an approved template
* [Broadcasts](https://docs.lazyapp.in/sending/broadcasts.md) — marketing templates at scale
* [Verify](https://docs.lazyapp.in/sending/verify.md) — prefer this over hand-rolled AUTHENTICATION templates
* [Template formats](https://docs.lazyapp.in/sending/template-formats.md) — carousels, GIFs, call buttons and more
* [Managing templates](https://docs.lazyapp.in/sending/template-management.md) — quality, groups, comparisons
* [Marketing messages](https://docs.lazyapp.in/sending/marketing-messages.md) — Meta's marketing route
* [Direct Send](https://docs.lazyapp.in/sending/direct-send.md) — utility messages without templates
* [Webhooks](https://docs.lazyapp.in/webhooks/events.md) — template lifecycle events


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