Template discovery
Read cached template lifecycle state and available Flow launch descriptors, then construct eligible template sends without exposing provider tokens.
Reviewed
Templates are discovered within the WhatsApp Business Account of the key's bound number. List and detail support exact name, language and category filters, status, and includeUnavailable; lists also use limit and after. The default returns approved templates with no provider-missing evidence. status accepts PENDING, APPROVED, REJECTED, PAUSED or DISABLED and overrides the approved default. includeUnavailable=true includes all statuses when no status is supplied, and permits provider-missing records even with an explicit status. Its default is false; query booleans use exact true or false. category accepts AUTHENTICATION, MARKETING or UTILITY. Definitions omit provider examples and upload handles. Discovery alone does not prove current send eligibility.
Cached responses expose status, quality, statusSyncedAt, qualitySyncedAt, lastSyncedAt, providerMissingAt and unavailableReason. Timestamps can be null. Unavailable reasons are provider_missing, status_pending, status_rejected, status_paused or status_disabled, otherwise null; provider-missing evidence takes precedence. These fields describe retained evidence, not a fresh provider guarantee. Inspection works in either inbound mode and never makes a template sendable.
Unavailable detail returns the cached definition without live provider-contract discovery or a flowLaunch descriptor. Available approved Flow detail retains the existing contract discovery below. Non-approved or provider-missing records do not trigger credential lookup or live provider reads.
Template detail can add flowLaunch. List items do not include this descriptor. Its fields describe the selected Flow button: buttonIndex, contractHash, providerFlowId, hosting (WAARU or EXTERNAL), action, optional screen, inputs and requiresActiveLogicFlow. Each input declares name, type, required and optional default. Types are string, number, boolean and string_array.
Send a discovered Form
If requiresActiveLogicFlow is true or unavailableReason is present, do not send that Form from the Developer API. For an eligible Form, add this top-level field to the normal template send. Replace the illustrative hash and inputs with the current detail descriptor:
{
"templateFlowLaunch": {
"buttonIndex": 0,
"contractHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"inputs": { "customer_name": "Asha", "quantity": 0, "subscribed": false }
}
}This fragment accompanies messaging_product, to, type: "template" and template; it is not a complete send request. Inputs must match the declared names and types, with at most 30 fields and a 32 KiB limit. Do not invent a provider Flow token or also send a legacy Flow action component. The server creates the token during final admission.
Unavailable definitions or invalid/stale launch descriptors return safe 422 template_action_unavailable; invalid descriptor combinations can return 400 invalid_template_flow_launch. Refresh detail before constructing a new send. An exact retry of an accepted send retains its original descriptor and idempotency key. Provider discovery failures return 502 template_provider_unavailable. Neither includes provider diagnostics.
Respect the approved template
Template send parameters must match the approved structure and positional or named bindings. Authentication templates require matching body and button codes of 1 to 15 characters. Supported variants include media/product carousels, location and offer/coupon template parameters. Carousel cards use explicit zero-based card_index and must match approved card count and component signatures; product cards require an available product in the linked catalog. These are template sends, not standalone catalog interactive messages.
Use the complete send schema on message requests for all supported component variants. Creating/editing templates, Forms and catalogs remains outside public API-key authoring. Template and phone approval, freshness, quality, consent and ownership checks still apply to every new send.
Endpoint details
Every request uses Authorization: Bearer $WAARU_API_KEY and a connected number in Developer API mode. The scope below is required. IDs and payloads are illustrative; replace them with your own permitted resources. See authentication and common errors, size limits and retries.
GET /v1/developer/templates
Required scope: templates:read
Requires templates:read.
| Parameter | Location | Required | Constraints |
|---|---|---|---|
limit | query | No | {"type":"integer","minimum":1,"maximum":100,"default":50} |
after | query | No | {"type":"string","maxLength":512} Use nextCursor from the previous response; keep filters unchanged. |
name | query | No | {"type":"string","maxLength":512} |
language | query | No | {"type":"string","maxLength":32} |
category | query | No | {"enum":["MARKETING","UTILITY","AUTHENTICATION"]} |
status | query | No | {"enum":["PENDING","APPROVED","REJECTED","PAUSED","DISABLED"]} |
includeUnavailable | query | No | {"type":"boolean","default":false} Inspect cached non-approved/provider-missing templates. Status may explicitly select a lifecycle state. Inspection never authorizes sending. |
Request
curl --fail-with-body --request GET 'https://api.waaru.app/v1/developer/templates' \
--header "Authorization: Bearer $WAARU_API_KEY"Send no request body.
Response: HTTP 200
{
"items": [
{
"id": "template_example",
"name": "order_update",
"language": "en",
"category": "UTILITY",
"parameterFormat": "POSITIONAL",
"components": [
{
"type": "BODY",
"text": "Your order {{1}} is ready."
}
],
"status": "APPROVED",
"quality": "GREEN",
"statusSyncedAt": "2026-10-01T09:00:00.000Z",
"qualitySyncedAt": "2026-10-01T09:00:00.000Z",
"lastSyncedAt": "2026-10-18T09:00:00.000Z",
"providerMissingAt": null,
"unavailableReason": null
}
],
"nextCursor": null
}| Field | Required | Type and constraints |
|---|---|---|
items | Yes | {"type":"array","maxItems":100} Nested fields are in the complete schema below. |
nextCursor | Yes | {"type":["string","null"]} |
Response body: complete JSON schema
Required fields appear in required. Omit optional fields unless needed. Null is accepted only where the schema includes it. Arrays describe their elements in items; variants appear in anyOf or oneOf.
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 191
},
"name": {
"type": "string"
},
"language": {
"type": "string"
},
"category": {
"type": "string"
},
"parameterFormat": {
"type": "string"
},
"components": {
"type": "array",
"items": {
"type": "object"
},
"maxItems": 100
},
"status": {
"enum": [
"PENDING",
"APPROVED",
"REJECTED",
"PAUSED",
"DISABLED"
]
},
"quality": {
"type": [
"string",
"null"
]
},
"statusSyncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"qualitySyncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastSyncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"providerMissingAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"unavailableReason": {
"type": [
"string",
"null"
]
}
},
"required": [
"id",
"name",
"language",
"category",
"parameterFormat",
"components",
"status",
"quality",
"statusSyncedAt",
"qualitySyncedAt",
"lastSyncedAt",
"providerMissingAt",
"unavailableReason"
],
"additionalProperties": false
},
"maxItems": 100
},
"nextCursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"items",
"nextCursor"
],
"additionalProperties": false
}Failures use a stable code and safe message. They never expose internal exception messages, traces or provider diagnostics. Check the error reference before retrying. An inaccessible resource can return 404 even if its identifier exists elsewhere.
GET /v1/developer/templates/{id}
Required scope: templates:read
Requires templates:read. Unavailable cached templates return lifecycle metadata without live Form discovery. Approved available templates may include the sanitized live launch contract. Neither inspection nor a null unavailableReason authorizes sending.
| Parameter | Location | Required | Constraints |
|---|---|---|---|
id | path | Yes | {"type":"string","minLength":1,"maxLength":191} |
name | query | No | {"type":"string","maxLength":512} |
language | query | No | {"type":"string","maxLength":32} |
category | query | No | {"enum":["MARKETING","UTILITY","AUTHENTICATION"]} |
includeUnavailable | query | No | {"type":"boolean","default":false} |
status | query | No | {"enum":["PENDING","APPROVED","REJECTED","PAUSED","DISABLED"]} |
Request
curl --fail-with-body --request GET 'https://api.waaru.app/v1/developer/templates/id_example' \
--header "Authorization: Bearer $WAARU_API_KEY"Send no request body.
Response: HTTP 200
{
"template": {
"id": "template_example",
"name": "order_update",
"language": "en",
"category": "UTILITY",
"parameterFormat": "POSITIONAL",
"components": [
{
"type": "BODY",
"text": "Your order {{1}} is ready."
}
],
"status": "APPROVED",
"quality": "GREEN",
"statusSyncedAt": "2026-10-01T09:00:00.000Z",
"qualitySyncedAt": "2026-10-01T09:00:00.000Z",
"lastSyncedAt": "2026-10-18T09:00:00.000Z",
"providerMissingAt": null,
"unavailableReason": null
}
}| Field | Required | Type and constraints |
|---|---|---|
template | Yes | {"type":"object","required":["id","name","language","category","parameterFormat","components","status","quality","statusSyncedAt","qualitySyncedAt","lastSyncedAt","providerMissingAt","unavailableReason"],"additionalProperties":false} Nested fields are in the complete schema below. |
Response body: complete JSON schema
Required fields appear in required. Omit optional fields unless needed. Null is accepted only where the schema includes it. Arrays describe their elements in items; variants appear in anyOf or oneOf.
{
"type": "object",
"properties": {
"template": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 191
},
"name": {
"type": "string"
},
"language": {
"type": "string"
},
"category": {
"type": "string"
},
"parameterFormat": {
"type": "string"
},
"components": {
"type": "array",
"items": {
"type": "object"
},
"maxItems": 100
},
"status": {
"enum": [
"PENDING",
"APPROVED",
"REJECTED",
"PAUSED",
"DISABLED"
]
},
"quality": {
"type": [
"string",
"null"
]
},
"statusSyncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"qualitySyncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastSyncedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"providerMissingAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"unavailableReason": {
"type": [
"string",
"null"
]
},
"flowLaunch": {
"type": "object",
"properties": {
"buttonIndex": {
"type": "integer",
"minimum": 0,
"maximum": 9
},
"contractHash": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"providerFlowId": {
"type": "string",
"pattern": "^[0-9]{1,32}$"
},
"hosting": {
"enum": [
"WAARU",
"EXTERNAL"
]
},
"action": {
"enum": [
"navigate",
"data_exchange"
]
},
"screen": {
"type": "string",
"maxLength": 128
},
"inputs": {
"maxItems": 30,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_]{0,63}$"
},
"type": {
"type": "string",
"enum": [
"string",
"number",
"boolean",
"string_array"
]
},
"required": {
"default": false,
"type": "boolean"
},
"default": {
"anyOf": [
{
"type": "string",
"maxLength": 4096
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"maxLength": 4096
}
}
]
}
},
"required": [
"name",
"type",
"required"
],
"additionalProperties": false
}
},
"requiresActiveLogicFlow": {
"type": "boolean"
},
"unavailableReason": {
"type": "string"
}
},
"required": [
"buttonIndex",
"contractHash",
"providerFlowId",
"hosting",
"action",
"inputs",
"requiresActiveLogicFlow"
],
"additionalProperties": false
}
},
"required": [
"id",
"name",
"language",
"category",
"parameterFormat",
"components",
"status",
"quality",
"statusSyncedAt",
"qualitySyncedAt",
"lastSyncedAt",
"providerMissingAt",
"unavailableReason"
],
"additionalProperties": false
}
},
"required": [
"template"
],
"additionalProperties": false
}Failures use a stable code and safe message. They never expose internal exception messages, traces or provider diagnostics. Check the error reference before retrying. An inaccessible resource can return 404 even if its identifier exists elsewhere.
Next: All public API operations or authentication and scopes.