# Make a template

> Saves a text with {{variables}}.

`POST /v1/templates`

Saves a text with `{{variables}}`. A variable is a contact field (`first_name`, `last_name`, `company`), one of their custom fields, or a value you pass when sending; `{{first_name|there}}` uses “there” when the contact has no first name. A variable that is empty with no fallback is left out, along with the space before it.

Needs a key with the `messages:send` scope. [Keys and scopes](https://usebluebot.com/docs/concepts/api-keys)

Send an `Idempotency-Key` so a retry never acts twice. [Idempotency](https://usebluebot.com/docs/concepts/idempotency)

## Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | No | Any string of up to 255 characters, new for each request. A retry with the same key gets the first answer back for 24 hours, with `Idempotent-Replayed: true`. Up to 255 characters |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | A name, unique among your templates whatever its capitals. Up to 80 characters |
| `body` | string | Yes | The text, up to 2,000 characters, with `{{variables}}` and `{{variable\|fallback}}`. Up to 2,000 characters |

## Example

```bash
curl -X POST "https://usebluebot.com/api/v1/templates" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70" \
  -d '{
    "name": "Showing reminder",
    "body": "Hi {{first_name|there}}, see you at {{showing}} tomorrow."
  }'
```

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

const template = await client.templates.create({
  name: 'Showing reminder',
  body: 'Hi {{first_name|there}}, see you at {{showing}} tomorrow.',
}, { idempotencyKey: '0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70' });
console.log(template);
```

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

template = client.templates.create(
    name="Showing reminder",
    body="Hi {{first_name|there}}, see you at {{showing}} tomorrow.",
    idempotency_key="0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70",
)
print(template)
```

## Response

`201`: The new template.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | A tpl_ id. Starts with `tpl_` |
| `object` | "template" | Yes |   |
| `name` | string | Yes | Its name, unique among your templates. |
| `body` | string | Yes | The text, with `{{variables}}` and `{{variable\|fallback}}`. |
| `variables` | array of strings | Yes | The variables the text names, in order, once each. |
| `created_at` | timestamp | Yes | When it was made. |
| `updated_at` | timestamp | Yes | When it last changed. |

The answer, `201`:

```json
{
  "id": "tpl_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "template",
  "name": "Showing reminder",
  "body": "Hi {{first_name|there}}, see you at {{showing}} tomorrow.",
  "variables": [
    "first_name",
    "showing"
  ],
  "created_at": "2026-11-10T16:00:00.000Z",
  "updated_at": "2026-11-10T16:00:00.000Z"
}
```

## Errors

Every refusal has the same envelope and a page of its own. These are the ones this operation can give.

| Code                                                        | Status | What it means                                                                                                                                    |
| ----------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`unauthorized`](https://usebluebot.com/docs/errors/unauthorized)                 | 401    | The request carried no key, or a key BlueBot doesn’t know: mistyped, revoked, or a test key sent to the live API.                                |
| [`insufficient_scope`](https://usebluebot.com/docs/errors/insufficient_scope)     | 403    | The key doesn’t carry the scope the endpoint needs. Each endpoint’s reference names its scope.                                                   |
| [`rate_limited`](https://usebluebot.com/docs/errors/rate_limited)                 | 429    | Too many requests. A key may make 120 requests a minute. The `RateLimit` header says how many are left, and `Retry-After` how long to wait.      |
| [`internal`](https://usebluebot.com/docs/errors/internal)                         | 500    | Something went wrong on BlueBot’s side. It was reported. Send the request again; with an Idempotency-Key that is always safe.                    |
| [`invalid_request`](https://usebluebot.com/docs/errors/invalid_request)           | 400    | Something in the request doesn’t fit: a missing or malformed field, a bad id, or a combination the endpoint refuses. `details` names each field. |
| [`conflict`](https://usebluebot.com/docs/errors/conflict)                         | 409    | The request doesn’t fit the thing’s current state, or another request with the same Idempotency-Key is still running.                            |
| [`idempotency_conflict`](https://usebluebot.com/docs/errors/idempotency_conflict) | 409    | The Idempotency-Key was used in the last 24 hours with a different request. A key stands for one request.                                        |

## Try it
