Por qué Meta rechazó la plantilla: get_template_status, no el GET de Waspy
GET /api/v2/templates no trae rejectionReason: el schema lo recorta. El motivo lo devuelve la herramienta MCP get_template_status, que lee Meta.
Respuesta corta: GET https://api.waspytech.com/api/v2/templates y GET /api/v2/templates/by-name/:name te dicen si la plantilla está rejected. No te dicen por qué. El schema público no incluye rejectionReason ni qualityScore, y el serializer de la v2 descarta esas claves aunque la fila las tenga. El motivo lo pedís con la herramienta MCP get_template_status (https://api.waspytech.com/mcp, scope templates:read), que lee Meta y actualiza la copia local. En el panel, el mismo texto está en la ficha de la plantilla.
La referencia del GET está en templates. El conector, en conectar MCP. Cómo se redacta para que no la rechacen está en por qué rechazan una plantilla.
La clave pública entra en Scale (US$150 por mes) y Enterprise. Starter y Growth no la incluyen. Los planes están en Planes y el módulo, en la API.
Qué devuelve el GET, y qué no
El handler lee la fila completa. La respuesta HTTP pasa por TemplatePublic. Ese objeto declara id, phoneNumberId, metaTemplateId, name, language, category, status, components y createdAt. Un z.object se queda con esas claves y tira el resto. Por eso el JSON de un rechazo se ve así, sin motivo:
{
"data": {
"id": "template-uuid",
"phoneNumberId": "channel-uuid",
"metaTemplateId": "1234567890",
"name": "pedido_despachado",
"language": "es_AR",
"category": "utility",
"status": "rejected",
"components": [
{ "type": "BODY", "text": "Hola {{1}}, tu pedido {{2}} ya salió." }
],
"createdAt": "2026-11-02T13:00:00.000Z"
},
"meta": { "requestId": "..." }
}
category en la base es minúscula (marketing, utility, authentication). La página de docs a veces la muestra en mayúscula. El valor que factura Meta es esa categoría, no el texto del motivo.
| Lo que sí ves | Lo que el JSON público no trae |
|---|---|
status: pending, approved, rejected, paused, disabled | rejectionReason |
components, con las {{1}} | rejectionDetails (código, título, descripción del webhook) |
category y language | qualityScore (GREEN, YELLOW, RED o null) |
metaTemplateId | tenantId, syncedAt, cardImageKeys, optInProofMediaKey |
GET /templates?status=rejected lista las rechazadas. Sigue sin el texto. El listado, by-name y el GET por UUID comparten el mismo recorte. El contrato del listado está en listar plantillas y el de by-name, en consultar por nombre.
get_template_status
No es GET /api/v2/.... Es una herramienta del MCP. Con una API key:
curl -sS -X POST https://api.waspytech.com/mcp \
-H "Authorization: Bearer $WASPY_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_template_status","arguments":{"name":"pedido_despachado","phoneNumberId":"CHANNEL_UUID"}}}'
CHANNEL_UUID es el id interno del número (GET /api/v2/channels), no el phone number id de Meta. El scope es templates:read. Si la clave tiene una lista de scopes y ese no está, la herramienta responde que la conexión no tiene el permiso. Los nombres están en scopes.
El result.structuredContent que arma el servidor trae:
| Campo | Qué es |
|---|---|
id | UUID interno de Waspy |
name | El nombre en Meta |
status | El estado que devolvió Meta, mapeado a minúscula |
language | Por ejemplo es_AR |
category | marketing, utility o authentication |
rejectionReason | El rejected_reason de Meta, o null si vino NONE o vacío |
status, category, rejectionReason y syncedAt en la fila. No es una lectura de cache. Si metaTemplateId empieza con sandbox:, la herramienta dice que la plantilla no está en Meta y no refresca.
Nombre, id, y la fila que elige
Hace falta name o id. Si no mandás ninguno, el texto es Pasá el nombre o el id de la plantilla.
idpuede ser el UUID de Waspy o el id de Meta (metaTemplateId).namebusca por nombre en la cuenta.phoneNumberIddesambigua si el mismo nombre está en dos números.- La búsqueda por nombre no recibe idioma y no ordena. Si
pedido_despachadoexiste enes_ARy enen_US, puede volver cualquiera de las dos. PasáphoneNumberIdy, si todavía hay dos idiomas en el mismo número, usá elid.
Si no hay fila, el texto es No encuentro esa plantilla en esta cuenta. No es el 404 TEMPLATE_NOT_FOUND del GET REST.
De dónde sale el texto del motivo
Hay dos escrituras, y no dejan el mismo string.
| Camino | Qué guarda en rejectionReason |
|---|---|
| Webhook de estado de plantilla | El código de Meta y, si vino una descripción distinta, esa descripción en la línea de abajo. También guarda rejectionDetails con reason, title y description |
Sync del número, o get_template_status | Solo rejected_reason, pasado por normalizeMetaNone. No escribe rejectionDetails |
NONE, vacío o ausente quedan en null. Si Meta aprueba o reincorpora la plantilla, el webhook pone rejectionReason y rejectionDetails en null. Un get_template_status posterior deja el motivo en null cuando Meta ya no manda uno.
qualityScore lo escribe el sync del número, desde quality_score.score. El panel lo muestra (verde, amarillo, rojo). get_template_status no lo devuelve y el GET REST tampoco.
Qué no hace esta llamada
| Querés | Qué hay |
|---|---|
| Crear o editar por REST | No hay POST ni PATCH /api/v2/templates |
| Alta de texto | Panel, o MCP create_template con templates:write. Valida variables y promo en una utilidad antes de llamar a Meta. No le escribe al cliente |
Webhook template.rejected en la API pública | No existe. Los eventos públicos son mensaje, estado de mensaje, conversación y contacto |
| Que Waspy le conteste al cliente porque la plantilla fue rechazada | No. El equipo reescribe. Un asistente externo puede llamar a esta herramienta; el mensaje al cliente lo confirma una persona |
create_template chequea (variable al inicio o al final, dos variables juntas, promo dentro de una utilidad) están en crear una plantilla con MCP. Cómo conectar Claude, Cursor, Codex o Grok está en el conector.
Qué cobra Meta cuando después sí sale
Leer el motivo no entrega un mensaje. Cuando la plantilla queda approved y la mandás, Meta cobra por mensaje entregado. En Argentina, desde el 1 de octubre de 2026:
| Categoría | Dólares | Pesos |
|---|---|---|
| Marketing | US$0,0618 | ARS 89,56 |
| Utilidad o autenticación | US$0,026 | ARS 37,68 |
La API v2 permite 120 pedidos por minuto por cuenta. get_template_status además gasta una llamada a Meta por cada refresco. Una pasada cuando el panel muestra “rechazada” alcanza. Un loop por cada nombre del catálogo no.
