curl --request POST \
--url https://api.flow.engineer/v1/templates \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"name": "<string>",
"language": "<string>",
"components": [
{
"text": "<string>",
"buttons": [
{
"text": "<string>",
"url": "<string>",
"phone": "<string>"
}
]
}
]
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
sender: 'snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T',
name: '<string>',
language: '<string>',
components: [
{
text: '<string>',
buttons: [{text: '<string>', url: '<string>', phone: '<string>'}]
}
]
})
};
fetch('https://api.flow.engineer/v1/templates', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.flow.engineer/v1/templates"
payload = {
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"name": "<string>",
"language": "<string>",
"components": [
{
"text": "<string>",
"buttons": [
{
"text": "<string>",
"url": "<string>",
"phone": "<string>"
}
]
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.flow.engineer/v1/templates"
payload := strings.NewReader("{\n \"sender\": \"snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T\",\n \"name\": \"<string>\",\n \"language\": \"<string>\",\n \"components\": [\n {\n \"text\": \"<string>\",\n \"buttons\": [\n {\n \"text\": \"<string>\",\n \"url\": \"<string>\",\n \"phone\": \"<string>\"\n }\n ]\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"id": "tpl_01JB8Z9X2D4F6H8K0M2P4R6T8V",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"name": "<string>",
"language": "<string>",
"category": "marketing",
"status": "pending",
"components": [
{
"type": "header",
"format": "text",
"text": "<string>",
"buttons": [
{
"type": "quick_reply",
"text": "<string>",
"url": "<string>",
"phone": "<string>"
}
]
}
],
"created_at": "2023-11-07T05:31:56Z",
"rejection_reason": "<string>"
}{
"error": {
"type": "invalid_request",
"message": "limit must be between 1 and 100.",
"hint": "Pass limit between 1 and 100 (default 20), and page with after or before.",
"doc_url": "https://api.flow.engineer/docs/errors/invalid_request",
"param": "limit"
}
}{
"error": {
"type": "authentication",
"message": "No valid API key was given.",
"hint": "Send the header Authorization: Bearer fk_test_... (or fk_live_...); no key yet? Get a test key with curl -X POST https://api.flow.engineer/v1/sandbox/keys",
"doc_url": "https://api.flow.engineer/docs/errors/authentication"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}{
"error": {
"type": "unsupported_content",
"message": "iMessage cannot show buttons.",
"hint": "Set \"fallback\": \"auto\" to send numbered text instead, or send text.",
"doc_url": "https://api.flow.engineer/docs/errors/unsupported_content",
"param": "content.type"
}
}{
"error": {
"type": "new_contact_limit",
"message": "This sender has started its 15 new conversations for today.",
"hint": "Retry after 3600 seconds; replies into existing conversations still go.",
"doc_url": "https://api.flow.engineer/docs/errors/new_contact_limit",
"retry_after": 3600,
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}Create a template
Submits a WhatsApp message template for review for one of your dedicated
WhatsApp senders. It starts as pending; you receive
template.status_changed when WhatsApp approves, rejects or pauses it. Templates
are WhatsApp only: a Telegram or iMessage sender gets
422 unsupported_content, and a name and language the sender already has
gets 400 invalid_request.
curl --request POST \
--url https://api.flow.engineer/v1/templates \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"name": "<string>",
"language": "<string>",
"components": [
{
"text": "<string>",
"buttons": [
{
"text": "<string>",
"url": "<string>",
"phone": "<string>"
}
]
}
]
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
sender: 'snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T',
name: '<string>',
language: '<string>',
components: [
{
text: '<string>',
buttons: [{text: '<string>', url: '<string>', phone: '<string>'}]
}
]
})
};
fetch('https://api.flow.engineer/v1/templates', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.flow.engineer/v1/templates"
payload = {
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"name": "<string>",
"language": "<string>",
"components": [
{
"text": "<string>",
"buttons": [
{
"text": "<string>",
"url": "<string>",
"phone": "<string>"
}
]
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.flow.engineer/v1/templates"
payload := strings.NewReader("{\n \"sender\": \"snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T\",\n \"name\": \"<string>\",\n \"language\": \"<string>\",\n \"components\": [\n {\n \"text\": \"<string>\",\n \"buttons\": [\n {\n \"text\": \"<string>\",\n \"url\": \"<string>\",\n \"phone\": \"<string>\"\n }\n ]\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"id": "tpl_01JB8Z9X2D4F6H8K0M2P4R6T8V",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"name": "<string>",
"language": "<string>",
"category": "marketing",
"status": "pending",
"components": [
{
"type": "header",
"format": "text",
"text": "<string>",
"buttons": [
{
"type": "quick_reply",
"text": "<string>",
"url": "<string>",
"phone": "<string>"
}
]
}
],
"created_at": "2023-11-07T05:31:56Z",
"rejection_reason": "<string>"
}{
"error": {
"type": "invalid_request",
"message": "limit must be between 1 and 100.",
"hint": "Pass limit between 1 and 100 (default 20), and page with after or before.",
"doc_url": "https://api.flow.engineer/docs/errors/invalid_request",
"param": "limit"
}
}{
"error": {
"type": "authentication",
"message": "No valid API key was given.",
"hint": "Send the header Authorization: Bearer fk_test_... (or fk_live_...); no key yet? Get a test key with curl -X POST https://api.flow.engineer/v1/sandbox/keys",
"doc_url": "https://api.flow.engineer/docs/errors/authentication"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}{
"error": {
"type": "unsupported_content",
"message": "iMessage cannot show buttons.",
"hint": "Set \"fallback\": \"auto\" to send numbered text instead, or send text.",
"doc_url": "https://api.flow.engineer/docs/errors/unsupported_content",
"param": "content.type"
}
}{
"error": {
"type": "new_contact_limit",
"message": "This sender has started its 15 new conversations for today.",
"hint": "Retry after 3600 seconds; replies into existing conversations still go.",
"doc_url": "https://api.flow.engineer/docs/errors/new_contact_limit",
"retry_after": 3600,
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}Authorizations
An API key of one app, sent as Authorization: Bearer <key>. Keys start with
fk_test_ (test mode: sandbox senders and test data only) or fk_live_
(live mode). Keep live keys on your server; never ship them in an app or page.
Headers
A unique string (up to 255 characters) that makes this request safe to retry. A repeat with the same key within 24 hours returns the first answer instead of acting again. See "Idempotency" in the introduction.
1 - 255The API version to use, as a date. Without it, the version pinned to your app when it was created is used.
"2026-11-01"
Body
A template to submit to Meta.
A sender ID, snd_ and a ULID.
^snd_[0-9A-HJKMNP-TV-Z]{26}$"snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T"
Lowercase letters, digits and underscores.
^[a-z0-9_]{1,512}$The template's language, as Meta names it (en, en_US, hi).
Meta's category for the template, which sets its price.
marketing, utility, authentication The template's parts. A body is required.
1Show child attributes
Show child attributes
Response
The template was submitted.
A WhatsApp message template, mirrored from Meta with its approval status.
A template ID, tpl_ and a ULID.
^tpl_[0-9A-HJKMNP-TV-Z]{26}$"tpl_01JB8Z9X2D4F6H8K0M2P4R6T8V"
A sender ID, snd_ and a ULID.
^snd_[0-9A-HJKMNP-TV-Z]{26}$"snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T"
The template's name at Meta.
The template's language, as Meta names it.
Meta's category for the template, which sets its price.
marketing, utility, authentication A template's approval status at Meta.
pending, approved, rejected, paused, disabled The template's parts, as submitted.
Show child attributes
Show child attributes
When the template was submitted.
Meta's reason, when the template was rejected.