
# WhatsApp Templates API

WhatsApp message templates are pre-written messages that have been approved for sending outside the normal 24-hour conversation window — for example a welcome message, an appointment reminder, or a re-engagement nudge. This API lets you list, create, edit, submit, check, delete, and send templates programmatically.

All paths below are relative to the API base URL:

```
https://api.youraiconnector.com/v1
```

Every request must be authenticated. See [Authentication](authentication.md) for the four accepted methods. The examples on this page use the `X-API-Key` header (and one query-parameter form for cURL).

::: note
**Note:** Templates ride on the WhatsApp Business API channel, so this part of the API requires both API access and a plan that includes WhatsApp channels. Without them, requests are rejected with a `403`.
:::


---

## Working with sub-accounts (agencies)


---

## Approval states

Because messages sent outside an open conversation must be reviewed by WhatsApp first, every template carries an approval `status`:

| Status | Meaning |
|---|---|
| `draft` | Created or saved but not yet sent for review. You can still edit it. |
| `received` | Submitted and accepted into the review queue. |
| `pending` | Under review. |
| `approved` | Cleared for sending. |
| `rejected` | Turned down. The `rejection_reason` field explains why; fix it, then submit again. |

Only `draft` and `rejected` templates can be edited or (re)submitted. Once a template is `approved` it is locked — create a new one if you need changes.

> **Auto-approval:** Some channels do not require an external review step. Templates created or submitted for a campaign on such a channel are stored as `approved` immediately, with no content ID (`sid`).

---

## Templates on Meta-connected accounts

These endpoints work the same way whichever WhatsApp connection your account runs on, but what happens behind them differs:

- On a **managed WhatsApp connection**, templates are registered with the messaging provider and `sid` is the provider's content ID (`HXXXXXXXX…`).
- On an account whose number runs on **its own WhatsApp Business Account** (either Meta connection option), templates are created and reviewed **in that WhatsApp Business Account** and `sid` is Meta's own template id — a numeric string such as `"3394843740694756"`. `status` still uses the values in the table above, and `rejection_reason` still carries Meta's explanation.

Two extra endpoints exist for this: one to ask which connection you are on, and one to reconcile your template list with your WhatsApp Business Account. Templates that already exist in the WhatsApp Business Account are imported into your library by the sync, so a `GET /whatsapp-templates` afterwards lists them like any other template.

### Check which connection templates run on

`GET /whatsapp-templates/provider`

| Field | Description |
|---|---|
| `provider` | `twilio` when templates are registered with the managed messaging provider, `meta` when they live in your own WhatsApp Business Account. |
| `lane` | Which Meta connection is in use — `meta_cloud_api` (your own Meta app) or `meta_embedded` (connected through our Meta app). `null` on a managed connection. |
| `waba_id` | The WhatsApp Business Account templates are created in, or `null`. |
| `templates_enabled` | `false` when the Meta connection is not finished yet (no WhatsApp Business Account or access token stored). Creating or submitting templates fails with a `400` until it is. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/whatsapp-templates/provider?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/whatsapp-templates/provider", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/whatsapp-templates/provider",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "provider": "meta",
  "lane": "meta_cloud_api",
  "waba_id": "2357661648036355",
  "templates_enabled": true
}
```

### Sync templates from Meta

Refreshes the approval status of every template that lives in your WhatsApp Business Account, and imports any template that exists there but is not in your library yet. Safe to call as often as you like. On a managed connection there is nothing to sync, so the call does nothing and simply reports how many templates you have.

`POST /whatsapp-templates/meta-sync`

| Field | Description |
|---|---|
| `imported` | Templates found in the WhatsApp Business Account that were added to your library by this call. |
| `updated` | Existing templates whose status or details changed. |
| `total` | Templates in your library after the sync. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/meta-sync?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/whatsapp-templates/meta-sync", {
  method: "POST",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/meta-sync",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "provider": "meta",
  "imported": 2,
  "updated": 5,
  "total": 12
}
```

### Talking to Meta directly (advanced)

If you need something the endpoints above do not expose — template headers, footers, buttons, or a fully hand-built template — `/v1/meta-templates` passes your request straight through to Meta's own template API, without storing anything in your template library. It only works on accounts whose number runs on their own WhatsApp Business Account; on a managed connection every call returns `400` asking you to connect a Meta app first.

| Endpoint | What it does |
|---|---|
| `GET /meta-templates` | Lists the templates on your WhatsApp Business Account with their latest status. Add `?name=` to filter to one exact template name. Returns `{ "success": true, "templates": [...] }`. |
| `POST /meta-templates` | Creates a template and submits it for Meta review in one step. Requires `name`, `language`, and `body` (or a complete `components` array instead of `body`). Optional: `variables` (array of strings), `category` (`MARKETING`, `UTILITY`, or `AUTHENTICATION`), `header`, `footer`, `buttons`. Returns `201` with `{ "success": true, "template": {...} }`. |
| `DELETE /meta-templates/{name}` | Deletes the template by its Meta name — **every language** of it. Add `?hsm_id=` with Meta's template id to remove a single language instead. Returns `{ "success": true, "name": "..." }`. |

A template Meta refuses returns `400` with Meta's own explanation in `error`.

---

## List templates

Returns all templates on your account, with a lightweight summary of each.

`GET /whatsapp-templates`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/whatsapp-templates?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/whatsapp-templates", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/whatsapp-templates",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": [
    {
      "id": "template_abc123",
      "name": "welcome_message",
      "status": "approved",
      "language": "en",
      "body": "Hi {{first_name}}, thanks for reaching out!"
    },
    {
      "id": "template_def456",
      "name": "appointment_reminder",
      "status": "pending",
      "language": "en",
      "body": "Hi {{first_name}}, this is a reminder about your appointment."
    }
  ]
}
```

---

## Get a template

Returns the full detail of a single template, including its variables, status, and timestamps.

`GET /whatsapp-templates/{templateId}`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "template": {
    "id": "template_abc123",
    "name": "welcome_message",
    "body": "Hi {{first_name}}, thanks for reaching out!",
    "language": "en",
    "variables": ["first_name"],
    "status": "approved",
    "sid": "HXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "type": "general",
    "category": "marketing",
    "rejection_reason": null,
    "campaign_id": "campaign123",
    "date_created": "2026-06-01T10:00:00.000Z",
    "date_updated": "2026-06-02T08:30:00.000Z",
    "submitted_at": "2026-06-01T10:05:00.000Z",
    "approved_at": "2026-06-02T08:30:00.000Z"
  }
}
```

A template that does not exist on your account returns `404` with `{ "success": false, "error": "Template not found" }`.

---

## Create a template

Creates a template for a campaign's opening message and submits it for approval in one step.

`POST /whatsapp-templates`

| Field | Required | Description |
|---|---|---|
| `campaign_id` | Yes | The campaign the template belongs to. |
| `name` | Yes | A name for the template. |
| `language` | Yes | Language code, for example `en`, `es`, `de`, `pt_BR`, `zh_CN`. |
| `body` | Yes | The message text, up to 1024 characters. |
| `variables` | No | Ordered list of variable names used in the body. |

Variable placeholders may be written as `{{first_name}}`, `{first_name}`, or `[first_name]` — they are all normalized to the double-curly form.

The result depends on the campaign's channels:

- **WhatsApp Business API campaign:** the content is sent for WhatsApp review. The response carries `campaign_status` (`received` or `pending`) and a `template_sid`.
- **A channel without an external review step:** the template is stored and auto-approved (`campaign_status: "approved"`, `template_sid: null`).
- **No WhatsApp channel on the campaign:** nothing is created and `campaign_status` is `not_applicable`.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "campaign123",
    "name": "welcome_message",
    "language": "en",
    "body": "Hi {{first_name}}, thanks for reaching out!",
    "variables": ["first_name"]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/whatsapp-templates", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    campaign_id: "campaign123",
    name: "welcome_message",
    language: "en",
    body: "Hi {{first_name}}, thanks for reaching out!",
    variables: ["first_name"],
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "campaign_id": "campaign123",
        "name": "welcome_message",
        "language": "en",
        "body": "Hi {{first_name}}, thanks for reaching out!",
        "variables": ["first_name"],
    },
)
data = res.json()
```

**Response** (submitted for review)

```json
{
  "success": true,
  "campaign_status": "pending",
  "template_sid": "HXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
```

---

## Create a standalone template

Creates a template in your template library without tying it to a campaign's opening message. This is the create step of the lifecycle the rest of this page follows: create it here, edit it, submit it for review, poll its status, and delete it when you no longer need it.

`POST /whatsapp-templates/docs`

| Field | Required | Description |
|---|---|---|
| `name` | Yes | A name for the template. |
| `language` | Yes | Language code, for example `en`, `es`, `de`, `pt_BR`, `zh_CN`. |
| `body` | Yes | The message text, up to 1024 characters. |
| `variables` | No | Ordered list of variable names used in the body. |
| `status` | No | `draft` (default) stores it without submitting; `submitted` queues it for WhatsApp review straight away. |
| `type` | No | `general` (default) or `smart_followup`. |
| `category` | No | `marketing`, `utility`, `authentication`, or `authentication-international`. |
| `campaign_id` | No | Links the template to one of your campaigns. |

> **Authentication (one-time code) templates.** WhatsApp does not accept free-text authentication templates: the message body is preset by WhatsApp and the template must carry a "copy code" button. When you create a template with `category: "authentication"`, we submit it in that fixed shape for you. Your `body` is kept as the preview shown in the app, but the text your contact receives is WhatsApp's own wording (the code, a security reminder, and a 10-minute expiry note). Declare exactly one variable, for example `["code"]`, and pass the code when you send (see the `variables` field on [Send a template to a contact](#send-a-template-to-a-contact)). The code must be shorter than 15 characters.

> **Which create should I use?** Use this one when you want a template you can edit and submit yourself. Use `POST /whatsapp-templates` (above) when you want to set a campaign's opening message — that one requires `campaign_id` and writes straight into the campaign.

A template created as `submitted` is sent for WhatsApp review in the background, so check the status endpoint for the outcome rather than expecting it in the response.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/docs?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "welcome_message",
    "language": "en",
    "body": "Hi {{first_name}}, thanks for reaching out!",
    "variables": ["first_name"],
    "status": "draft",
    "category": "marketing"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/whatsapp-templates/docs", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "welcome_message",
    language: "en",
    body: "Hi {{first_name}}, thanks for reaching out!",
    variables: ["first_name"],
    status: "draft",
    category: "marketing",
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/docs",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "name": "welcome_message",
        "language": "en",
        "body": "Hi {{first_name}}, thanks for reaching out!",
        "variables": ["first_name"],
        "status": "draft",
        "category": "marketing",
    },
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "template_id": "template_abc123",
  "status": "draft"
}
```

A missing `name`, `language`, or `body`, an unsupported language, a `status` other than `draft` or `submitted`, an unknown `type` or `category`, or a body over 1024 characters returns `400` with an explanatory `error`. A `campaign_id` that is not one of your campaigns returns `404`.

---

## Update a template

Edits a template that has not been approved yet. Only templates with status `draft` or `rejected` can be edited. Supply any combination of `name`, `body`, `language`, and `variables` — only the fields you send are changed.

`PUT /whatsapp-templates/{templateId}`

> Editing does **not** resubmit the template for review. Use the submit endpoint afterwards.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Hi {{first_name}}, here is an update for you.",
    "variables": ["first_name"]
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      body: "Hi {{first_name}}, here is an update for you.",
      variables: ["first_name"],
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "body": "Hi {{first_name}}, here is an update for you.",
        "variables": ["first_name"],
    },
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "template_id": "template_abc123"
}
```

Trying to edit a template that is already `approved` (or otherwise not editable), sending no fields, or sending an invalid value returns `400` with an explanatory `error`.

---

## Submit a template for approval

Submits a `draft` or `rejected` template for review. Templates on a channel that does not require external review are approved immediately; all others are sent to WhatsApp and the returned `status` (usually `received` or `pending`) is stored on the template.

`POST /whatsapp-templates/{templateId}/submit`

> **Follow-up templates** must declare and use their required variables before they can be submitted: a first-name placeholder, plus a personal-context placeholder for smart follow-ups.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/submit" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/submit",
  { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/submit",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "template_id": "template_abc123",
  "status": "pending",
  "sid": "HXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
```

---

## Check approval status

A lightweight endpoint for polling a template's current status. The status is read from the stored record, which is refreshed periodically in the background, so a very recent approval or rejection can take a short while to appear.

`GET /whatsapp-templates/{templateId}/status`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/status" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/status",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/status",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "template_id": "template_abc123",
  "name": "welcome_message",
  "status": "approved",
  "sid": "HXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "rejection_reason": null,
  "date_updated": "2026-06-02T08:30:00.000Z"
}
```

---

## Delete a template

Removes the template record from your account.

`DELETE /whatsapp-templates/{templateId}`

::: warning
**Important:** On a managed connection only the stored record is removed — content that WhatsApp has already approved may remain registered with the messaging provider. On an account running on its own WhatsApp Business Account the template is deleted from that account as well. Either way, if a campaign still uses this template, re-point that campaign to another template **before** deleting, otherwise sends that rely on it will fail.
:::


**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123",
  { method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "template_id": "template_abc123",
  "note": "The template record was removed from your account. Content already approved by WhatsApp may remain registered with the messaging provider."
}
```

---

## Send a template to a contact

Sends an approved template to a contact, even when there is no open conversation — this reopens the chat session. You can target the contact by `contactId` or by `phoneNumber`, and choose the template by `whatsappTemplateId` or by `templateName`.

`POST /whatsapp-templates/send`

| Field | Required | Description |
|---|---|---|
| `contactId` | One of these two | The contact's ID. |
| `phoneNumber` | One of these two | The contact's phone number (with country code, no spaces). Looked up or created if needed. |
| `whatsappTemplateId` | One of these two | The template's ID. |
| `templateName` | One of these two | The template's name, as shown in the app. |
| `firstName` | No | Used to fill in a newly created contact. |
| `lastName` | No | Used to fill in a newly created contact. |
| `email` | No | Used to fill in a newly created contact. |
| `variables` | No | Explicit values for the template's variables, keyed by variable name, for example `{ "code": "482913" }`. A value given here wins over the contact's fields for that variable; variables you leave out are still filled from the contact as described below. This is how you pass a one-time code to an authentication template. |

The body of the template supports advanced variable substitution:

- **Basic variables:** `{{first_name}}`, `{{email}}`, `{{company}}`
- **Default values:** `{{first_name|there}}` shows `there` if the field is empty
- **Transformations:** `{{company|uppercase}}`, `{{name|lowercase}}`, `{{name|capitalize}}`
- **Combined:** `{{company|Your Company|uppercase}}`

> **Credits:** Sending a template consumes credits. The exact cost depends on the recipient's country and the template's category.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/send?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contactId": "contact123",
    "whatsappTemplateId": "template_abc123"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/whatsapp-templates/send", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contactId: "contact123",
    whatsappTemplateId: "template_abc123",
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/send",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "contactId": "contact123",
        "whatsappTemplateId": "template_abc123",
    },
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": "WhatsApp template message sent successfully"
}
```

A request missing both a contact identifier and both template identifiers returns `400`. If your account lacks the messaging credentials needed to send, the response is `403`.

---

## Create or update a campaign's live template

A second pair of endpoints for a campaign's opening template, scoped by path instead of by a `campaign_id` in the body. These are the ones to use for a campaign that is already live: unlike [Create a template](#create-a-template) above, updating here also re-submits the campaign's follow-up drafts for review, so the opening template and its follow-ups stay in sync.

`POST /whatsapp-templates/campaign/{campaignId}` creates the campaign's opening template. `PUT /whatsapp-templates/campaign/{campaignId}` edits it — the campaign must already have a template, or this returns `400`.

| Field | Required | Description |
|---|---|---|
| `name` | Yes | A name for the template. |
| `language` | Yes | Language code, for example `en`, `es`, `de`, `pt_BR`, `zh_CN`. |
| `body` | Yes | The message text, up to 1024 characters. |
| `variables` | Yes | Ordered list of variable names used in the body. Pass an empty array if the template uses none. |

**cURL** (create)

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/campaign/campaign123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "welcome_message",
    "language": "en",
    "body": "Hi {{first_name}}, thanks for reaching out!",
    "variables": ["first_name"]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/whatsapp-templates/campaign/campaign123", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "welcome_message",
    language: "en",
    body: "Hi {{first_name}}, thanks for reaching out!",
    variables: ["first_name"],
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/campaign/campaign123",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "name": "welcome_message",
        "language": "en",
        "body": "Hi {{first_name}}, thanks for reaching out!",
        "variables": ["first_name"],
    },
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "campaign_status": "pending",
  "template_sid": "HXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "message": "WhatsApp template created and campaign updated successfully."
}
```

To edit, swap the method to `PUT` and the same fields — this re-submits the opening template (and the campaign's follow-up drafts, on a WhatsApp API campaign) for review.

A campaign that does not belong to your account returns `404`; a campaign belonging to another account you're not authorized for returns `403`. Editing a campaign with no existing template returns `400`.

---

## Send a template to an existing contact

A simpler, path-scoped alternative to [Send a template to a contact](#send-a-template-to-a-contact) above: both the template and the contact must already exist — nothing is looked up by name or created on the fly.

`POST /whatsapp-templates/{templateId}/send-to-contact`

| Field | Required | Description |
|---|---|---|
| `contactId` | Yes | The contact's ID. Must belong to your account. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/send-to-contact?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactId": "contact123" }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/send-to-contact",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ contactId: "contact123" }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/send-to-contact",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"contactId": "contact123"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": "WhatsApp template message sent successfully"
}
```

> **Credits:** Sending consumes credits, priced the same way as the endpoint above. A `contactId` that is missing or not on your account returns `403`; a `templateId` that does not exist returns `404`.

---

## Bulk-send a template

Send one template to many contacts in a single call, with a cost preview you can show before committing.

### Estimate the cost first

Returns what sending would cost, broken down by destination country, without sending anything or moving any credits. Template pricing is per destination country, so this has to be computed server-side against the real contacts rather than estimated client-side.

`POST /whatsapp-templates/{templateId}/estimate-bulk-cost`

| Field | Required | Description |
|---|---|---|
| `contactIds` | Yes | Contacts to price, up to 500 per call. Duplicates are counted once. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/estimate-bulk-cost?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["contact123", "contact456"] }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/estimate-bulk-cost",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ contactIds: ["contact123", "contact456"] }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/estimate-bulk-cost",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"contactIds": ["contact123", "contact456"]},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": {
    "countries": [
      {
        "countryCode": "1",
        "name": "United States",
        "iso": "US",
        "flag": "🇺🇸",
        "contactCount": 120,
        "costPerContact": 0.5,
        "subtotal": 60.0
      }
    ],
    "totalContacts": 120,
    "totalTemplateCost": 60.0,
    "templateCategory": "marketing",
    "skippedContacts": 2
  }
}
```

`skippedContacts` counts ids that were missing, not yours, or held no phone number — the estimate covers only the rest, so a non-zero value means the real send will reach fewer contacts than you selected.

### Send the batch

Sends the template to every contact in the list, resolving any smart variables per contact and charging credits per send.

`POST /whatsapp-templates/{templateId}/bulk-send`

| Field | Required | Description |
|---|---|---|
| `contactIds` | Yes | Contacts to send to, up to 5000 per call. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/bulk-send?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["contact123", "contact456"] }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/bulk-send",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ contactIds: ["contact123", "contact456"] }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/template_abc123/bulk-send",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"contactIds": ["contact123", "contact456"]},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": { "sent": 118, "failed": 2, "total": 120 }
}
```

A contact that fails (not found, not on your account, or a send error) is skipped and counted in `failed` rather than stopping the batch. An empty `contactIds`, more than 5000 ids on a send (500 on an estimate), or a missing `templateId` returns `400`.

---

## Retry a failed message

Two endpoints for resending a message that failed, without creating a new message record or spending credits again.

`POST /whatsapp-templates/messages/{contactId}/{messageId}/retry-template` retries a failed template message specifically — it re-resolves the template content from the campaign if the failed message doesn't already carry it. Only messages with status `failed` and type `template` can be retried this way.

`POST /whatsapp-templates/messages/{contactId}/{messageId}/retry` is channel-agnostic and works for any failed non-template message (for example WhatsApp Web), dispatching to the right send path based on the message's channel. It accepts status `failed`, `failed_connection`, `limit_exceeded`, or `queued_retry`.

Neither endpoint takes a request body.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/messages/contact123/msg_abc789/retry-template?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/messages/contact123/msg_abc789/retry-template",
  { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/messages/contact123/msg_abc789/retry-template",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": "Message retry initiated successfully"
}
```

For the channel-agnostic version, swap the path to `.../msg_abc789/retry`. A message whose status isn't eligible for retry, or (on the template endpoint) that isn't a template message, returns `400`. A missing contact or message returns `404`.

---

## WhatsApp Business profile

Manage the WhatsApp Business profile (about, address, description, email, websites, business category, and logo) shown to contacts on WhatsApp. Works on both a managed connection and an account running its own WhatsApp Business Account.

### Save the profile

`PUT /whatsapp-templates/profile`

| Field | Required | Description |
|---|---|---|
| `phoneNumber` | Yes | The WhatsApp number this profile belongs to. Must be connected on your account. |
| `about` | No | Short "About" text shown on the profile. |
| `address` | No | Business address. |
| `description` | No | Longer business description. |
| `email` | No | Contact email shown on the profile. |
| `websites` | No | Array of website URLs. Each must be a valid URL. |
| `vertical` | No | Business category, for example `Retail` or `Professional Services`. |
| `profilePictureHandle` | No | The handle returned by the picture-upload endpoint below, to set the profile photo. |

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/whatsapp-templates/profile?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+31612345678",
    "about": "We reply within a few hours",
    "email": "support@example.com",
    "websites": ["https://example.com"]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/whatsapp-templates/profile", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    phoneNumber: "+31612345678",
    about: "We reply within a few hours",
    email: "support@example.com",
    websites: ["https://example.com"],
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/whatsapp-templates/profile",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "phoneNumber": "+31612345678",
        "about": "We reply within a few hours",
        "email": "support@example.com",
        "websites": ["https://example.com"],
    },
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": "WhatsApp Business profile updated successfully"
}
```

A missing `phoneNumber`, an invalid website URL, or a `phoneNumber` not connected on your account returns `400` or `404`.

### Upload a profile picture

Downloads an image from a URL you provide and uploads it to WhatsApp, returning a handle. Pass that handle as `profilePictureHandle` on the save-profile call above to set it as the photo — this endpoint only uploads the image, it does not set it by itself.

`POST /whatsapp-templates/profile/picture`

| Field | Required | Description |
|---|---|---|
| `phoneNumber` | Yes | The WhatsApp number this profile belongs to. |
| `fileUrl` | Yes | A publicly reachable URL to the image to upload. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/whatsapp-templates/profile/picture?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+31612345678",
    "fileUrl": "https://example.com/logo.png"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/whatsapp-templates/profile/picture", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    phoneNumber: "+31612345678",
    fileUrl: "https://example.com/logo.png",
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/whatsapp-templates/profile/picture",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "phoneNumber": "+31612345678",
        "fileUrl": "https://example.com/logo.png",
    },
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": "1234567890123456"
}
```

`data` is the uploaded picture's handle. A missing `phoneNumber` or `fileUrl`, or a `phoneNumber` with no WhatsApp access token on file, returns `400`; an unreachable or invalid `fileUrl` returns an error describing why the download failed.

---

## Check a sender's status

Polls (and refreshes) a connected WhatsApp number's live sending status with the messaging provider. Useful for confirming a number is actually able to send before you rely on it.

`GET /whatsapp-templates/sender-status/{phoneNumber}`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/whatsapp-templates/sender-status/+31612345678" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/whatsapp-templates/sender-status/+31612345678",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/whatsapp-templates/sender-status/+31612345678",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": "ONLINE"
}
```

`data` is one of `ONLINE` (sending normally), `PENDING` (still being verified), or `DELETED` (the provider no longer recognizes this sender — reconnect the number). A `phoneNumber` with no WhatsApp business information on file returns `404`.

---

## Generate follow-up templates with AI

The platform can write a campaign's WhatsApp follow-up templates for you — the nudges sent when a conversation goes quiet — from the campaign's own instructions and goal. There is one job endpoint that runs in the background, plus three older endpoints kept for existing integrations. All of them use AI credits.

### Start a generation job

`POST /campaigns/{campaignId}/template-generation`

| Field | Required | Description |
|---|---|---|
| `type` | No | `all` (the default) writes the whole follow-up set. `cold_only` writes only the messages for contacts who never replied. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/campaign_abc123/template-generation?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "all" }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/campaign_abc123/template-generation",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ type: "all" }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/campaigns/campaign_abc123/template-generation",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"type": "all"},
)
data = res.json()
```

**Response** (`202`)

```json
{ "success": true, "campaign_id": "campaign_abc123", "type": "all" }
```

The call returns as soon as the job is queued. Read the campaign (`GET /campaigns/{campaignId}`, see the [Campaigns API](campaigns.md)) and watch its `template_generation_status` object until it finishes:

| Field | Description |
|---|---|
| `status` | `processing` while the job runs, then `completed` or `failed`. |
| `progress` | 0 to 100. |
| `current_template`, `total_templates` | How many templates have been written so far, out of how many the job will write — 11 for an outgoing or combined campaign, 9 otherwise. |
| `error` | Why a `failed` job stopped, for example not enough credits. |
| `started_at`, `completed_at` | When the job began and ended. |

The generated templates land on the campaign like any other, so they show up in [List templates](#list-templates) and still go through WhatsApp approval before they can be sent. A `400` means `type` was something other than `all` or `cold_only`; a `404` means the campaign does not exist or belongs to another account.

Agents have a twin of this call, `POST /agents/{agentId}/template-generation`, which writes the follow-ups for an Agent and finishes during the call in the usual case — see [Generate follow-up messages](agents.md#generate-follow-up-messages) in the AI Agents API.

### The older generation endpoints

Three earlier endpoints do the same work and are kept so existing integrations keep running. New code should use the job endpoint above.

| Endpoint | What it does |
|---|---|
| `POST /whatsapp-templates/campaign/{campaignId}/generate-async` | Starts follow-up generation for the campaign in the background and returns `202` with `{ "success": true, "data": { "result": "success", "message": "..." } }`. Credits are charged up front (skipped on an account that brings its own AI key) and the campaign's `template_generation_status` reports progress exactly as above. |
| `POST /whatsapp-templates/campaign/{campaignId}/generate-followups` | Generates all nine follow-up templates during the call — for a campaign created before automatic follow-ups existed, or one that needs them written again — and returns `200` with `templatesGenerated` inside `data`. |
| `POST /whatsapp-templates/agent/{agentId}/generate-followups` | The same synchronous generation addressed by Agent. The response adds `agent_id`, `campaign_id` and `target`: `"campaign"` when the templates were written onto the Agent's campaign, `"agent"` (with `campaign_id: null`) when the Agent has no campaign and they were stored on the Agent itself. A missing or foreign Agent is a `404`. |

All three need automatic follow-ups on the account and enough credits — a `400` names which is missing — and the campaign-addressed pair return `403` when the campaign belongs to another account.

---

## Templates API errors

Template endpoints return the standard error envelope:

```json
{
  "success": false,
  "error": "Template not found"
}
```

A `404` on these endpoints usually means the resource wasn't found — either it doesn't exist or it belongs to another account. A few endpoints (the campaign-scoped create/update, and sends to an existing contact) return `403` instead when the campaign or contact belongs to someone else rather than not existing at all. Some endpoints also include an `error_code` field mirroring the HTTP status. The shared codes every endpoint can return — `400`, `401`, `403` (your plan does not include API access), `429` (rate limit) and `500` — are listed with retry guidance in [Errors & Pagination](errors-and-pagination.md).

---

## Next steps

- [Authentication](authentication.md) — the four ways to authenticate a request.
- [Errors & Rate Limits](errors-and-pagination.md) — status codes and the 300 req/min limit.
- [Campaigns API](campaigns.md) — manage the campaigns templates are attached to.
