---
title: "Upsert a language's content"
canonical: "https://bird.com/docs/api/reference/upsert-whatsapp-template-language"
---

# Upsert a language's content

`PUT /v1/whatsapp/templates/{template_ref}/versions/{version_id}/languages/{language}`

Writes one language of the template's draft, creating it when absent (`201`) and replacing it when present (`200`). A full replacement, so send the language's complete content every time. Only the draft is writable: submitted versions are immutable, and a language under active review at Meta refuses the write until that review finishes. Pass `revision` to make the write a compare-and-set against the copy you read. Tags are canonicalised, and the response echoes the canonical form: use it in later calls for this language.

## Servers

- `https://{region}.platform.bird.com`: Regional API endpoint. Use the host for the region your organization is hosted in. Official Bird SDKs and the CLI select it automatically from your API key, so you rarely need to set it by hand.
  - `region`: `us1`, `eu1`. Default: `us1`. The region your organization's data is hosted in.
- `https://platform.bird.com`: Region-independent endpoint for authentication and account administration.
- `http://localhost:8080`: Local development.

## Authentication

Use one authentication option:

- `BearerAuth`: HTTP bearer authentication. Pass the API key as a bearer token in the `Authorization` header. Workspace
keys use `bk_{region}_*`; organization master keys use `bm_{region}_*`.
The prefix identifies the region and selects the
API endpoint. Official Bird SDKs and the CLI derive the region from the key.

- `CookieAuth`: Send `bird_session` in cookie. Session cookie set after signing in to the Bird dashboard. The cookie
value is an opaque session token; no session data is stored in the cookie
itself.

## Code samples

**TypeScript**

```ts
const saved = await bird.whatsapp.templates.versions.languages.set(
  "order_update",
  "wav_01krdgeqcxet5s7t44vh8rt9mg",
  "es",
  {
    components: [
      {
        type: "body",
        text: "Tu pedido {{order_id}} ya fue enviado.",
        example_parameters: [{ type: "text", name: "order_id", text: "A-4192" }],
      },
    ],
  },
);
console.log(saved.language, saved.revision);
```

Examples: [TypeScript](/docs/api/reference/upsert-whatsapp-template-language.ts.md) · [Python](/docs/api/reference/upsert-whatsapp-template-language.py.md) · [Go](/docs/api/reference/upsert-whatsapp-template-language.go.md) · [PHP](/docs/api/reference/upsert-whatsapp-template-language.php.md) · [CLI](/docs/api/reference/upsert-whatsapp-template-language.cli.md) · [MCP](/docs/api/reference/upsert-whatsapp-template-language.mcp.md) · [cURL](/docs/api/reference/upsert-whatsapp-template-language.curl.md)

## Example response `200`

```json
{
  "language": "pt-BR",
  "revision": 4,
  "content_hash": "sha256:9f2c4e1a7b03d85fbc6e29d417a05e8c3b1d9f76a2e4c018d53b7f9a6c2e18d4"
}
```

## Path parameters

- `template_ref` (string, required): Template ID (`wat_` prefix) or slug. A value that parses as a valid ID resolves by ID; any other value resolves as a slug.
- `version_id` (string, required): ID of the template version (`wav_` prefix), as returned by the version list.
- `language` (string, required): The language, as a BCP-47 tag. Case and separator variance is accepted and normalised, and the canonical form is returned.

## Header parameters

- `Idempotency-Key` (string, optional)

  Client-supplied key. On operations supporting request deduplication, a retained
  response is replayed for duplicate requests with the same key within the
  idempotency window (3 hours by default). This protection requires a workspace,
  organization, or staff-account scope. User-only and unscoped unauthenticated operations,
  streams, and operations with a separate replay contract do not use this
  response replay.

  On a supported operation, if idempotency protection is unavailable before execution, the API returns
  `503 IdempotencyUnavailable` (E01033) without executing this attempt. Retry with
  backoff using the same key and request. An operation that takes effect before
  its response is retained can still execute again on retry.

  Two distinct 409 errors signal misuse:

  - `request_in_progress` (E01004): The same key is currently being
    processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds.
  - `idempotency_key_reuse` (E01005): The same key has already completed
    against a different request body or method. Generate a new key.

  Recommended key format is `<event-type>/<entity-id>` (for example `welcome-user/usr_abc123`).
- `X-Workspace-Id` (string, optional): Workspace context for the request. Required for dashboard authentication and master keys. Workspace keys and access tokens already identify their workspace; send that workspace or omit the header. A different one is rejected.
- `X-Organization-Id` (string, optional): Organization context for the request. Required for dashboard authentication. An API key or access token carries its own organization, so send either that organization or no header at all; a different one is rejected.

## Request body

- `components` (array of object): The template's content blocks for this language. Header, footer and buttons are optional. Every variable needs an example value, which is what Meta reviews against. May be omitted or empty on a draft; a body block is required before the language can be submitted.
- `components.type` (string, required)

  A content block's type. A `body` block is required on every language; `header`, `footer`, `buttons` and `carousel` are optional and appear at most once each. A `carousel` holds the cards a marketing template scrolls through, and is the one block type that nests: its cards carry blocks of their own. Any other value is rejected.

  Possible values: `header`, `body`, `footer`, `buttons`, `carousel`
- `components.format` (string)

  The header's kind. Required on a `header` block and refused on any other.

  Possible values: `text`, `image`, `video`, `gif`, `document`, `location`
- `components.text` (string): The block's text, with each variable written as a placeholder. Send it on a `body`, a `footer`, and a `text` header. Four blocks refuse it. A `buttons` block carries text on the buttons themselves. A media or `location` header shows a file or map and has nowhere to put copy. A `carousel` carries text on its cards. For an authentication template's body and footer, WhatsApp writes the copy from the two settings below. May be empty on a draft; required before submission.
- `components.add_security_recommendation` (boolean): Ends an authentication template's body with WhatsApp's own advice not to share the code. Send it only on an authentication template's body block, whose copy is otherwise WhatsApp's to write.
- `components.code_expiration_minutes` (integer): How long the passcode stays valid. Send it on an authentication template's footer block and WhatsApp writes the footer stating it; omit the footer block entirely to leave it off. WhatsApp enforces the range it accepts and refuses the submission otherwise.
- `components.example_parameters` (array of object): An example value per `{{n}}` in `text`, in placeholder order. Meta reviews the filled message rather than the template, so a block with variables and no examples is refused. A media header also carries its file here as one entry. Its `type` matches the header's `format`, and its `url` addresses the file. A media header has no placeholder, so this entry is the header's own sample rather than a value that fills one.
- `components.example_parameters.type` (string, required)

  The kind of value a template parameter accepts. `text` is an example filling a `{{n}}` placeholder. The other four are the sample file a media header shows, and each matches its header's `format`. Any other value is rejected.

  Possible values: `text`, `image`, `video`, `gif`, `document`
- `components.example_parameters.text` (string): The example value. Send it when `type` is `text`. May be empty on a draft; required before submission.
- `components.example_parameters.url` (string): The address of the file a media header shows. Send it when `type` is `image`, `video`, `gif` or `document`. We fetch the file when the language is submitted and hand Meta our own copy, so the address only has to be reachable at submission. WhatsApp reviews the template with this file, and the message each recipient receives carries the file named on the send. May be empty on a draft, for a file not yet chosen; a reachable address is required before submission.
- `components.example_parameters.name` (string): The named placeholder this example fills. Send it only for a template written against named parameters rather than positional `{{n}}` ones. May be empty on a draft; required before submission.
- `components.buttons` (array of object): The buttons this block carries. Send it only on a `buttons` block. Ten in total, of which at most two open a link and at most one dials a number. Quick replies must sit together rather than being interleaved with the other types, so `Quick reply, Quick reply, Link` is accepted where `Quick reply, Link, Quick reply` is refused. A template with four or more buttons, or one that mixes a quick reply with another type, cannot be opened on WhatsApp's desktop clients, and the recipient is asked to switch to their phone. May be omitted or empty on a draft; at least one button is required before submission.
- `components.buttons.type` (string, required)

  A button's behavior.

  - `url`: opens a link.
  - `quick_reply`: sends its own label back to you as an inbound message.
  - `phone_number`: dials the number it carries.
  - `otp`: copies a one-time passcode. It belongs only on an authentication
    template, and that template takes no other button type.
  - `copy_code`: copies a coupon code to the recipient's clipboard. It belongs
    only on a marketing template, which takes at most one.
  - `request_contact_info`: asks the recipient to share the phone number their
    WhatsApp account carries. It belongs only on a utility or marketing
    template, as that template's only button.

  Any other value is rejected.

  Possible values: `url`, `quick_reply`, `phone_number`, `otp`, `copy_code`, `request_contact_info`
- `components.buttons.otp_type` (string)

  How the recipient receives the passcode. Send it only on an `otp` button.

  Possible values: `copy_code`
- `components.buttons.text` (string)

  The button's label, which each button type treats differently.

  - `url`, `quick_reply`, `phone_number`: your own label. Required.
  - `otp`: omit it. WhatsApp writes this label itself and reveals it only
    once the language has been submitted.
  - `copy_code`: omit it. WhatsApp fixes this label and does not let you
    change it.
  - `request_contact_info`: omit it. WhatsApp fixes this label too and
    writes `Share Contact Info`, which is also what a submitted button
    reads back as, so a read button can be written back unchanged.

  A draft takes any label, so this may be empty on a draft and is required
  only before submission. A label a fixed-label button may not carry is
  refused rather than silently replaced: the submit comes back as
  `valid: false` with an `errors` entry naming this field, in every
  language.
- `components.buttons.url` (string): The address a `url` button opens. An address with a placeholder is personalized per recipient, and one without is the same for everyone. At most one variable, and it must sit at the end of the address, written as `{{1}}`. A button address is numbered this way even on a template whose text uses named variables. A variable needs its example value in `example_parameters`. May be empty on a draft; required before submission.
- `components.buttons.phone_number` (string): The number a `phone_number` button dials, in international format. Some countries publish numbers with a leading zero after the country calling code; WhatsApp strips that zero, so give a number that dials without it. May be empty on a draft; required before submission.
- `components.buttons.example_parameters` (array of object): The value that fills the variable in `url`, which WhatsApp reviews in the filled address. Send only the value, so a `{{1}}` standing for an order number takes `A-4192`. Required whenever `url` carries a variable. Also required on a `copy_code` button as one `text` entry. The entry carries a sample coupon code of at most 20 characters for WhatsApp to review. Each recipient copies the code you provide when sending the message.
- `components.buttons.example_parameters.type` (string, required)

  The kind of value a template parameter accepts. `text` is an example filling a `{{n}}` placeholder. The other four are the sample file a media header shows, and each matches its header's `format`. Any other value is rejected.

  Possible values: `text`, `image`, `video`, `gif`, `document`
- `components.buttons.example_parameters.text` (string): The example value. Send it when `type` is `text`. May be empty on a draft; required before submission.
- `components.buttons.example_parameters.url` (string): The address of the file a media header shows. Send it when `type` is `image`, `video`, `gif` or `document`. We fetch the file when the language is submitted and hand Meta our own copy, so the address only has to be reachable at submission. WhatsApp reviews the template with this file, and the message each recipient receives carries the file named on the send. May be empty on a draft, for a file not yet chosen; a reachable address is required before submission.
- `components.buttons.example_parameters.name` (string): The named placeholder this example fills. Send it only for a template written against named parameters rather than positional `{{n}}` ones. May be empty on a draft; required before submission.
- `components.cards` (array of object): The cards a `carousel` block scrolls through. Send it only on a `carousel` block, and only on a marketing template. Every card must carry the same blocks in the same order, and a card's header shows an `image` or a `video`. A `document` header works at the top of a template but not on a card. The number of cards is fixed once the language is approved. A template approved with three cards always sends three. Changing the count requires a new template rather than a new version of this one. May be omitted or hold fewer than two on a draft; at least two are required before submission.
- `components.cards.components` (array of object): This card's content blocks, in display order. A card carries a `header` showing an image or a video, and optionally a `body` and a `buttons` block. Every card in the carousel must carry the same blocks, and the same buttons, in the same order, so a card with body text means every card needs body text. May be omitted or empty on a draft; a header block is required before submission.
- `components.cards.components.type` (string, required)

  A card block's type. A card carries a `header` showing an image or a video, and optionally a `body` and a `buttons` block. A card has no footer and cannot hold a carousel of its own, which is why a card's blocks are a narrower set than a template's.

  Possible values: `header`, `body`, `buttons`
- `components.cards.components.format` (string)

  The card header's kind. Required on a card's `header` block and refused on any other.

  Possible values: `image`, `video`
- `components.cards.components.text` (string): The card block's text, with each variable written as a placeholder. Send it on a `body` block. If any card carries body text then every card must, so the cards keep an even height. May be empty on a draft; required before submission.
- `components.cards.components.example_parameters` (array of object): An example value per `{{n}}` in `text`, in placeholder order. Meta reviews the filled message rather than the template, so a block with variables and no examples is refused. A card's `header` block carries its file here as a single entry whose `type` matches the header's `format` and whose `url` addresses the file, the same way a template header does.
- `components.cards.components.example_parameters.type` (string, required)

  The kind of value a template parameter accepts. `text` is an example filling a `{{n}}` placeholder. The other four are the sample file a media header shows, and each matches its header's `format`. Any other value is rejected.

  Possible values: `text`, `image`, `video`, `gif`, `document`
- `components.cards.components.example_parameters.text` (string): The example value. Send it when `type` is `text`. May be empty on a draft; required before submission.
- `components.cards.components.example_parameters.url` (string): The address of the file a media header shows. Send it when `type` is `image`, `video`, `gif` or `document`. We fetch the file when the language is submitted and hand Meta our own copy, so the address only has to be reachable at submission. WhatsApp reviews the template with this file, and the message each recipient receives carries the file named on the send. May be empty on a draft, for a file not yet chosen; a reachable address is required before submission.
- `components.cards.components.example_parameters.name` (string): The named placeholder this example fills. Send it only for a template written against named parameters rather than positional `{{n}}` ones. May be empty on a draft; required before submission.
- `components.cards.components.buttons` (array of object): The buttons this card carries, at most two. Send it only on a card's `buttons` block. Every card must carry the same buttons in the same order. A card takes a `quick_reply`, `phone_number` or `url` button; a `copy_code` or `request_contact_info` button belongs on the template itself.
- `components.cards.components.buttons.type` (string, required)

  A button's behavior.

  - `url`: opens a link.
  - `quick_reply`: sends its own label back to you as an inbound message.
  - `phone_number`: dials the number it carries.
  - `otp`: copies a one-time passcode. It belongs only on an authentication
    template, and that template takes no other button type.
  - `copy_code`: copies a coupon code to the recipient's clipboard. It belongs
    only on a marketing template, which takes at most one.
  - `request_contact_info`: asks the recipient to share the phone number their
    WhatsApp account carries. It belongs only on a utility or marketing
    template, as that template's only button.

  Any other value is rejected.

  Possible values: `url`, `quick_reply`, `phone_number`, `otp`, `copy_code`, `request_contact_info`
- `components.cards.components.buttons.otp_type` (string)

  How the recipient receives the passcode. Send it only on an `otp` button.

  Possible values: `copy_code`
- `components.cards.components.buttons.text` (string)

  The button's label, which each button type treats differently.

  - `url`, `quick_reply`, `phone_number`: your own label. Required.
  - `otp`: omit it. WhatsApp writes this label itself and reveals it only
    once the language has been submitted.
  - `copy_code`: omit it. WhatsApp fixes this label and does not let you
    change it.
  - `request_contact_info`: omit it. WhatsApp fixes this label too and
    writes `Share Contact Info`, which is also what a submitted button
    reads back as, so a read button can be written back unchanged.

  A draft takes any label, so this may be empty on a draft and is required
  only before submission. A label a fixed-label button may not carry is
  refused rather than silently replaced: the submit comes back as
  `valid: false` with an `errors` entry naming this field, in every
  language.
- `components.cards.components.buttons.url` (string): The address a `url` button opens. An address with a placeholder is personalized per recipient, and one without is the same for everyone. At most one variable, and it must sit at the end of the address, written as `{{1}}`. A button address is numbered this way even on a template whose text uses named variables. A variable needs its example value in `example_parameters`. May be empty on a draft; required before submission.
- `components.cards.components.buttons.phone_number` (string): The number a `phone_number` button dials, in international format. Some countries publish numbers with a leading zero after the country calling code; WhatsApp strips that zero, so give a number that dials without it. May be empty on a draft; required before submission.
- `components.cards.components.buttons.example_parameters` (array of object): The value that fills the variable in `url`, which WhatsApp reviews in the filled address. Send only the value, so a `{{1}}` standing for an order number takes `A-4192`. Required whenever `url` carries a variable. Also required on a `copy_code` button as one `text` entry. The entry carries a sample coupon code of at most 20 characters for WhatsApp to review. Each recipient copies the code you provide when sending the message.
- `components.cards.components.buttons.example_parameters.type` (string, required)

  The kind of value a template parameter accepts. `text` is an example filling a `{{n}}` placeholder. The other four are the sample file a media header shows, and each matches its header's `format`. Any other value is rejected.

  Possible values: `text`, `image`, `video`, `gif`, `document`
- `components.cards.components.buttons.example_parameters.text` (string): The example value. Send it when `type` is `text`. May be empty on a draft; required before submission.
- `components.cards.components.buttons.example_parameters.url` (string): The address of the file a media header shows. Send it when `type` is `image`, `video`, `gif` or `document`. We fetch the file when the language is submitted and hand Meta our own copy, so the address only has to be reachable at submission. WhatsApp reviews the template with this file, and the message each recipient receives carries the file named on the send. May be empty on a draft, for a file not yet chosen; a reachable address is required before submission.
- `components.cards.components.buttons.example_parameters.name` (string): The named placeholder this example fills. Send it only for a template written against named parameters rather than positional `{{n}}` ones. May be empty on a draft; required before submission.
- `revision` (integer): The revision you last read for this language, making the write a compare-and-set: a mismatch is refused with a conflict, leaving an edit you have not seen in place. Omit it for last-write-wins, which is what a single writer wants. Supplying it for a language that does not exist is also refused with a conflict rather than recreating the language. Inside a template's initial languages seed on create, a value here is refused, since no prior revision exists yet to compare against.

## Response body

- `language` (string, required): The canonical form of the tag you addressed. Use it in later calls for this language.
- `revision` (integer, required): The language's new revision, to pass back on your next write.
- `content_hash` (string, required): A hash over the content as saved, so you can tell whether your next write would change anything without diffing the document.
- `updated_at` (string, required): When this language was last written.

## Related resources

- [Should I use a Bird SDK or call the API directly?](/explained/platform/should-i-use-an-sdk-or-call-the-api-directly) (answer)
- [Build your first integration](/learn/paths/integration) (course)
- [Send your first email](/docs/get-started/send-your-first-email) (docs)

[Get an implementation brief](/learn/workspace?topic=api-basics)
