Lynkist Developers

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.

ScopeEndpoints
templates:readGET /templates, GET /templates/{id}
templates:writePOST /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

FieldTypeNotes
idUUIDLynkist's internal template ID. Pass to campaigns.
template_namestring (1-255)The name Meta knows it by. Lowercase, snake_case.
languagestring (2-10)Locale tag, e.g. en_US, hi, en_GB.
categorystringMeta category: MARKETING, UTILITY, AUTHENTICATION.
statusstringdraft, pending, approved, rejected, disabled.
componentsobject[] | nullMeta template-component objects (header, body, buttons, etc.).
created_atISO-8601
updated_atISO-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:

ComponentKeyShape
BODYexample.body_textOne row: a list with one value per variable, in order.
HEADER (TEXT)example.header_textA list with exactly one value.
HEADER (media)example.header_handleThe handle from Media → template session.
URL buttonexampleA 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

ParamTypeDefault
cursorstring—
limit1-10020
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

FieldTypeRequiredNotes
template_namestringYesLowercase letters, digits and underscores.
languagestringYesLocale tag, e.g. en_US, hi.
categorystringYesMARKETING, UTILITY or AUTHENTICATION.
componentsobject[]YesExactly 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

FieldTypeRequiredNotes
categorystringNoMARKETING, UTILITY or AUTHENTICATION.
componentsobject[]NoThe 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.

StatusEdit allowed?
draftYes, at any time. Nothing goes to Meta until you submit.
pendingNo. Wait for Meta's review to finish (400, TEMPLATE_LOCKED).
approvedYes, 1 edit in 24 hours and 10 edits in 30 days (rolling windows).
pausedSame limit as approved.
rejectedYes, no limit. Fix the reason Meta gave.
disabledNo (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

Statuserror.codeWhen
400VALIDATION_ERRORA missing or invalid field, a bad component shape, a variable without its example value, or illegal name characters.
400TEMPLATE_LOCKED, INVALID_TEMPLATE_STATEThe template's status does not allow the edit.
400TEMPLATE_LANGUAGE_ALREADY_EXISTSA template with the same template_name and language exists.
401INVALID_API_KEY, API_KEY_EXPIREDThe API key is missing, wrong, revoked or expired.
403INSUFFICIENT_SCOPEThe key lacks templates:read or templates:write.
404TEMPLATE_NOT_FOUNDThe template does not exist, or is not approved yet (read endpoints).
404WHATSAPP_NOT_CONNECTEDNo active WhatsApp account is connected.
429TEMPLATE_EDIT_QUOTA_EXCEEDEDMeta's edit limit for an approved template. See error.edit_quota.next_edit_at.
429RATE_LIMIT_EXCEEDEDYour rate limit. Wait for Retry-After.
502, 503UPSTREAM_ERROR, SERVICE_UNAVAILABLEA failure on our side or at WhatsApp. Retry with backoff.

See Errors for the response envelope.

Templates API | Lynkist Developers