---
title: "Submit a template version for review"
canonical: "https://bird.com/docs/api/reference/submit-whatsapp-template-version"
---

# Submit a template version for review

`POST /v1/whatsapp/templates/{template_ref}/versions/{version_id}/submit`

Validates the template draft before freezing it. If validation fails, the
API returns `200 OK` with every problem across all languages and creates no
version. If validation passes, the API freezes an immutable version and
returns `202 Accepted`. Only the languages whose content differs from their
approved copy are sent to Meta and marked `pending`; the rest carry their
existing verdict forward untouched, so a version whose languages are all
unchanged comes back already settled. A language an earlier version carried
but the draft dropped is deleted at Meta in the background. It is absent
from the new version, so it has no `pending` state. The version becomes live when any
language is approved, and no replacement draft opens automatically.

Set `validate_only: true` to run the same checks without freezing or
submitting a version. Local validation cannot predict Meta's decision. A
`202 Accepted` response confirms the version was frozen and accepted. Poll the
version or the template's `pending_version_id` for the verdicts on whichever
languages it marked `pending`.

## 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 draftId = "wav_01krdgeqcxet5s7t44vh8rt9mg";
const check = await bird.whatsapp.templates.versions.submit("order_update", draftId, {
  validate_only: true,
});
for (const problem of check.errors) console.log(problem.language, problem.field, problem.message);
if (check.valid) {
  const submitted = await bird.whatsapp.templates.versions.submit("order_update", draftId);
  console.log(submitted.version?.id);
}
```

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

## Example response `200`

```json
{
  "errors": [
    {
      "language": "pt-BR",
      "field": "components[2].cards[0].components[1].example_parameters[0].url",
      "code": "E15025",
      "message": "A body block needs text."
    }
  ],
  "version": {
    "id": "wav_01krdgeqcxet5s7t44vh8rt9mg",
    "template_id": "wat_01krdgeqcxet5s7t44vh8rt9mg",
    "version_number": 4,
    "submitted_at": "2026-07-20T11:04:00Z",
    "languages": {
      "en": {
        "status": "approved",
        "components": [
          {
            "type": "body",
            "text": "Your verification code is {{1}}.",
            "example_parameters": [
              {
                "type": "text",
                "text": "123456"
              }
            ]
          }
        ]
      }
    },
    "created_at": "2026-07-20T10:31:00Z"
  },
  "next": [
    {
      "kind": "operation"
    }
  ]
}
```

## 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.

## 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

- `validate_only` (boolean): Check the draft without submitting it. Every language is checked and every problem reported, but nothing is frozen and nothing is sent to WhatsApp. A clean result does not guarantee WhatsApp approval: WhatsApp offers no way to ask in advance, so it can still refuse a submission these checks passed. Use a different idempotency key for the validation and the submission. Reusing one key with different request bodies is rejected.

## Response body

- `valid` (boolean, required): Whether the version passed every check.
- `errors` (array of object, required): Every problem found across every language. One round trip identifies all problems. Empty when `valid` is true.
- `errors.language` (nullable string): The language the problem belongs to, or null when it concerns the version as a whole.
- `errors.field` (nullable string): Where the problem sits inside the language's content, as a dotted path with indexes, or null when it is not about one field. The path is rooted at the language you wrote, so `components[0].text` is the first block's text.
- `errors.code` (string, required): The error code a real submit fails with. Look it up in the error catalog for what to do about it.
- `errors.message` (string, required): What is wrong, in a form you can show the author.
- `version` (nullable object): The version this submit froze, or null when nothing was frozen: a check run, or a submit that found problems.
- `version.id` (string, required): Stable Bird identifier for the version.
- `version.template_id` (string, required): The template this version belongs to.
- `version.version_number` (nullable integer): The version's sequence number, assigned when it is submitted. Null on a draft, which has not been submitted and has no place in the sequence yet.
- `version.submitted_at` (nullable string, required): When this version was submitted to Meta. Null on a draft, which has not been submitted, and null for a built-in template's version, which Bird ships already approved rather than submitting on your behalf.
- `version.languages` (object, required): This version's content, keyed by BCP-47 language tag, with what its submission did with each language.
- `version.created_at` (nullable string, required): When the version was opened. Null for a built-in template's version, which Bird ships rather than stores.
- `next` (array of object)

  What to do next with this outcome. Present on reads that compute it: an empty list
  means there is nothing to do, and the field is absent entirely on responses that do
  not report next actions.

  A failed check routes back to the languages that need an edit; a clean one routes to
  the real submit, and then to polling the template for WhatsApp's verdicts.
- `next.kind` (string, required)

  What you do about this step.

  - `operation`: call the operation named in `operation`, then
    read again.
  - `external`: act somewhere this API does not reach, then read
    again.
  - `wait`: nothing is asked of you, so read again later.
  - `terminal`: nothing you do resolves this, so stop retrying.

  Tolerate a value you do not recognize: show the `description` and
  offer no action.

  Possible values (may grow over time): `operation`, `external`, `wait`, `terminal`
- `next.description` (string, required): A short, human-readable label for the step, suitable for display.
- `next.operation` (string): The operationId to call. Present only when `kind` is `operation`. The operation's own schema says how to call it; this says only which one, and what to address it with.
- `next.params` (object): The parameters that address the operation, by name: `{"sender_id": "…"}` for an operation on `/v1/sms/senders/{sender_id}/requirements`. A parameter the operation takes in its query string is given the same way, so an operation addressed as `?subject_id=` carries `{"subject_id": "…"}`. Every parameter the call needs is here, whether its value came from the thing you were acting on or is fixed for this step, so you can make the call from this object alone. Present only when `kind` is `operation` and the operation names a subject. A request body, when the operation takes one, is described by the operation's own schema and never appears here.
- `next.url` (string): A URL to open. Present only when `kind` is `external`, and only when the step has one. An external step whose `description` says to go and do something with no URL to open is normal.

## 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)
