Templates
Create, edit, submit for Meta approval, and list approved WhatsApp message templates.
WhatsApp messaging is template-first. Before you can POST /messages/template, you need a
template that Meta has approved. This API mirrors the approval workflow: draft → submit →
(Meta reviews) → approved.
| Scope | Endpoints |
|---|---|
templates:read | GET /templates, GET /templates/{id} |
templates:write | POST /templates, PUT /templates/{id}, POST /templates/{id}/submit |
Base path: /api/v1/public/templates
Read endpoints return approved templates only. Drafts and pending-review templates are not visible to the Public API list/get endpoints. To inspect a draft, fetch its details from the dashboard.
Template object
| Field | Type | Notes |
|---|---|---|
id | UUID | Lynkist's internal template ID. Pass to campaigns. |
template_name | string (1-255) | The name Meta knows it by. Lowercase, snake_case. |
language | string (2-10) | Locale tag, e.g. en_US, hi, en_GB. |
category | string | Meta category: MARKETING, UTILITY, AUTHENTICATION. |
status | string | draft, pending, approved, rejected, disabled. |
components | object[] | null | Meta template-component objects (header, body, buttons, etc.). |
created_at | ISO-8601 | |
updated_at | ISO-8601 | null |
Component shape
Each component is one of HEADER, BODY, FOOTER or BUTTONS. The body is required; the
others are optional. Variables are written as {{1}}, {{2}}, … and are replaced at send time
by the components array on POST /messages/template.
Every variable needs an example value. Meta reviews the template with these values, and
Lynkist refuses a template without them (400 VALIDATION_ERROR, with error.details naming the
component). Where the example goes depends on the component:
| Component | Key | Shape |
|---|---|---|
BODY | example.body_text | One row: a list with one value per variable, in order. |
HEADER (TEXT) | example.header_text | A list with exactly one value. |
HEADER (media) | example.header_handle | The handle from Media → template session. |
URL button | example | A list with exactly one value, for the {{1}} in url. |
A component without variables needs no example.
{
"components": [
{
"type": "HEADER",
"format": "TEXT",
"text": "Order {{1}} update",
"example": { "header_text": ["ORD-1042"] }
},
{
"type": "BODY",
"text": "Hi {{1}}, your order {{2}} has shipped. Track it: {{3}}.",
"example": {
"body_text": [["Anurag", "ORD-1042", "https://example.com/track/ORD-1042"]]
}
},
{ "type": "FOOTER", "text": "Reply STOP to opt out." },
{
"type": "BUTTONS",
"buttons": [
{
"type": "URL",
"text": "Track",
"url": "https://example.com/track/{{1}}",
"example": ["ORD-1042"]
},
{ "type": "PHONE_NUMBER", "text": "Call us", "phone_number": "+15550100" }
]
}
]
}The full grammar follows Meta's WhatsApp template component reference.
GET /templates — List approved templates
Templates whose status is approved, newest first. Uses cursor pagination.
Query parameters
| Param | Type | Default |
|---|---|---|
cursor | string | — |
limit | 1-100 | 20 |
curl "https://lynkist.io/api/v1/public/templates?limit=20" \
-H "Authorization: Bearer $LYNKIST_API_KEY"200 OK
{
"data": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"template_name": "order_confirmation",
"language": "en_US",
"category": "UTILITY",
"status": "approved",
"components": [ /* see Component shape */ ],
"created_at": "2026-04-12T09:00:00Z",
"updated_at": "2026-04-12T11:14:33Z"
}
],
"pagination": { "next_cursor": null, "has_more": false, "limit": 20 }
}GET /templates/{id} — Get an approved template
curl https://lynkist.io/api/v1/public/templates/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
-H "Authorization: Bearer $LYNKIST_API_KEY"200 OK — the template object. 404 if not found or not approved.
POST /templates — Create a draft template
Creates a draft. The template is not yet sendable — call POST /templates/{id}/submit to
push it to Meta for review.
The full request body. components is Meta's template-component array, as in
Component shape:
curl -X POST https://lynkist.io/api/v1/public/templates \
-H "Authorization: Bearer $LYNKIST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_name": "payment_confirm",
"language": "en_US",
"category": "UTILITY",
"components": [
{
"type": "BODY",
"text": "Hi {{1}}, we have received your payment of {{2}} towards {{3}}. Reference number: {{4}}. Thank you!",
"example": {
"body_text": [["John Doe", "₹500.00", "Order #12345", "REF98765"]]
}
}
]
}'Request body
| Field | Type | Required | Notes |
|---|---|---|---|
template_name | string | Yes | Lowercase letters, digits and underscores. |
language | string | Yes | Locale tag, e.g. en_US, hi. |
category | string | Yes | MARKETING, UTILITY or AUTHENTICATION. |
components | object[] | Yes | Exactly one BODY. Each variable needs its example value. |
201 Created — returns the draft template with status: "draft".
Naming rules (enforced by Meta)
- Lowercase letters, digits and underscores only.
- 1-512 characters.
- Must be unique within your WABA.
If you submit a draft Meta rejects, the status becomes rejected and the dashboard surfaces
Meta's rejection reason.
PUT /templates/{id} — Edit a template
Changes the category or the components of a template. Send only the field you change; the
other one is kept. template_name and language cannot change — Meta keys a template by them.
To change them, create a new template.
curl -X PUT https://lynkist.io/api/v1/public/templates/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
-H "Authorization: Bearer $LYNKIST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"components": [
{
"type": "BODY",
"text": "Hi {{1}}, your order {{2}} is confirmed and will ship today.",
"example": { "body_text": [["Anurag", "ORD-1042"]] }
}
]
}'Request body
| Field | Type | Required | Notes |
|---|---|---|---|
category | string | No | MARKETING, UTILITY or AUTHENTICATION. |
components | object[] | No | The complete new array (not a patch). Same rules as create. |
You cannot change category and components in the same request.
When you can edit
These are Meta's rules. Lynkist applies them before it calls Meta.
| Status | Edit allowed? |
|---|---|
draft | Yes, at any time. Nothing goes to Meta until you submit. |
pending | No. Wait for Meta's review to finish (400, TEMPLATE_LOCKED). |
approved | Yes, 1 edit in 24 hours and 10 edits in 30 days (rolling windows). |
paused | Same limit as approved. |
rejected | Yes, no limit. Fix the reason Meta gave. |
disabled | No (400, INVALID_TEMPLATE_STATE). |
Meta reviews an edit of an approved template again. Meta can refuse a change of category on an approved template; Lynkist then returns Meta's reason.
Over the limit, the call returns 429 with code: TEMPLATE_EDIT_QUOTA_EXCEEDED, and
error.edit_quota.next_edit_at tells you when the next edit is allowed:
{
"success": false,
"error": {
"code": "TEMPLATE_EDIT_QUOTA_EXCEEDED",
"message": "Meta allows one edit per 24 hours for an approved template. …",
"edit_quota": {
"applies": true,
"can_edit": false,
"used_30d": 1,
"remaining_30d": 9,
"next_edit_at": "2026-10-17T09:00:00+00:00",
"reason": "daily"
}
}
}Lynkist counts only the edits made through Lynkist. An edit made in Meta Business Manager also uses the allowance, so Meta can refuse an edit that Lynkist allowed.
200 OK — the template object, plus edit_quota (the allowance after this edit; for a
draft, applies is false).
POST /templates/{id}/submit — Submit for approval
Pushes a draft to Meta for review. Lynkist forwards the components verbatim — fix any rejection reasons on a new draft (Meta does not allow re-submitting a rejected template).
curl -X POST https://lynkist.io/api/v1/public/templates/f47ac10b-58cc-4372-a567-0e02b2c3d479/submit \
-H "Authorization: Bearer $LYNKIST_API_KEY"200 OK — returns the template, now in status: "pending".
Meta typically takes 1 minute to 24 hours to review. When Meta finishes, your template moves to
approved, rejected, or (rarely) disabled. Today there is no Lynkist webhook for the
approval transition — poll GET /templates/{id} (or watch the dashboard) until the status
changes. Template approval webhooks are on the roadmap.
Errors
| Status | error.code | When |
|---|---|---|
400 | VALIDATION_ERROR | A missing or invalid field, a bad component shape, a variable without its example value, or illegal name characters. |
400 | TEMPLATE_LOCKED, INVALID_TEMPLATE_STATE | The template's status does not allow the edit. |
400 | TEMPLATE_LANGUAGE_ALREADY_EXISTS | A template with the same template_name and language exists. |
401 | INVALID_API_KEY, API_KEY_EXPIRED | The API key is missing, wrong, revoked or expired. |
403 | INSUFFICIENT_SCOPE | The key lacks templates:read or templates:write. |
404 | TEMPLATE_NOT_FOUND | The template does not exist, or is not approved yet (read endpoints). |
404 | WHATSAPP_NOT_CONNECTED | No active WhatsApp account is connected. |
429 | TEMPLATE_EDIT_QUOTA_EXCEEDED | Meta's edit limit for an approved template. See error.edit_quota.next_edit_at. |
429 | RATE_LIMIT_EXCEEDED | Your rate limit. Wait for Retry-After. |
502, 503 | UPSTREAM_ERROR, SERVICE_UNAVAILABLE | A failure on our side or at WhatsApp. Retry with backoff. |
See Errors for the response envelope.