Message Template Validator
communication.template_validate
Overview
Nothing here is sent, nothing is rendered and nothing is connected to: we read a template, find every placeholder in it, and tell you which ones your allowlist does not cover. The question is permission rather than renderability, and the two are genuinely different — a template reading {{customer.ssn}} renders perfectly well and may still be something no message of yours is allowed to contain, which is why the verdict comes from the list you supply and never from the data you happen to hold. Four placeholder styles are understood: the double curly braces of {{name}}, the single braces of {name}, the dollar form of ${name}, and percent signs around %name%. Anything delimited like a placeholder that is not a plain name — a section such as {{#if premium}}, a filter, a call, an opener with no closer — comes back to you as an unsupported form rather than being quietly ignored, because a template that looks fully satisfied and then renders with control syntax still in it is the worse outcome. You also get the reverse check: name the placeholders a template must carry and we will tell you which of them are absent, which is how an unsubscribe link or an order number stops going missing. The same scanner backs our merge skill and our prompt-variable skill, so all three agree exactly on what a placeholder is.
Template placeholders are where a message stops being written by you and starts being written by your data. A name nobody approved slips in during a hurried edit, renders beautifully, and puts a card number or an internal note in front of a customer. The reverse costs you too: an unsubscribe link that quietly disappears from a marketing template is a compliance problem nobody notices until the complaints arrive. Both are decidable from the template alone, before any data is anywhere near it. The scanner behind this is a plain index walk with no pattern compiled from your template, which matters when templates arrive from customers and reach a million characters, and it is the same scanner our merge skill uses — so a template that validates here renders with exactly the placeholders you were told about.
Reliability
The same template and the same allowlist always produce the same report, in the same order, with no clock and no network anywhere in it. The template is never returned, never logged and never rendered; findings carry a name, a count and character offsets only, and up to 64 characters of an unsupported form so you can recognise it. Only one syntax is read per call. Five things stop the call instead of answering it. TEMPLATE_INVALID means the template was empty, so there was nothing to check — send the template and retry. TEMPLATE_TOO_LARGE means it passed 1000000 characters; split the document or check the section you care about, because retrying the same text will always be refused. TEMPLATE_PLACEHOLDER_LIMIT_EXCEEDED means more than 2000 placeholders and placeholder-shaped forms together, which usually means the syntax is wrong for the file rather than that the template is genuinely that large — check the syntax first, then split. TEMPLATE_SYNTAX_UNSUPPORTED means syntax named something other than MUSTACHE, BRACE, DOLLAR_BRACE or PERCENT, and we refuse rather than fall back, because a template checked under the wrong delimiters reports no placeholders at all and looks clean — correct the value and retry. TEMPLATE_ALLOWLIST_INVALID means one of the two name lists was longer than 2000 entries, or held an entry that is not a string of 1 to 256 characters — correct the list and retry. A template full of forbidden placeholders is never one of these: it is a normal success with valid false and the findings inside.
Context footprint
The answer is roughly proportional to the number of distinct placeholders rather than to the size of the template, so a long document with six names answers in well under a kilobyte. maxIssues caps the findings and the summary counts stay complete, so you always know how much you did not see. A successful call costs 1 execution credit.
When to use it
In review or in your build, whenever a template changes, and before you spend effort assembling data for a template you have not yet checked.
When you want the rendered text, when your templating language needs its control syntax evaluated, or when you have no policy about which names are acceptable.
Scope
Non-goals
- Does not send, queue or deliver anything, and opens no connection of any kind
- Does not render the template — communication.template_merge does that
- Does not evaluate a section, a loop, a filter or a conditional; those are reported as unsupported forms rather than interpreted
- Does not check spelling, grammar or tone
- Does not decide whether a name is sensitive; the allowlist you supply is the whole of the policy
- Does not repair a template or hand back a corrected one
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content, but it does not target sensitive data and returns only a structured non-sensitive result.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response does not contain submitted content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| template | string | Yes | Template text whose placeholder names are validated. |
| allowedVariables | array | Yes | Bounded required allowlist of variable names accepted by the template. The item shape is defined by the nested schema and the array contains from 0 through 10000 items. |
| requiredVariables | array | No | Bounded optional required variable names; omit it to require only referenced names. The item shape is defined by the nested schema and the array contains from 0 through 10000 items. |
| syntax | string | No | Placeholder syntax; omit it for MUSTACHE. |
| caseSensitive | boolean | No | Whether names are case-sensitive; omit it for true. |
| allowUnsupportedForms | boolean | No | Whether unsupported control forms remain warnings; omit it for false. |
| wildcards | boolean | No | Whether allowlist wildcards are enabled; omit it for false. |
| maxIssues | integer | No | Issue cap; omit it for one thousand. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| valid | boolean | Yes | Whether the template satisfies every enabled rule. |
| unknown | array | Yes | Bounded unknown placeholder names in first-use order. The item shape is defined by the nested schema and the array contains from 0 through 10000 items. |
| missing | array | Yes | Bounded required-but-unreferenced names. The item shape is defined by the nested schema and the array contains from 0 through 10000 items. |
| unsupportedForms | array | Yes | Bounded unsupported placeholder form receipts in source order. The item shape is defined by the nested schema and the array contains from 0 through 10000 items. |
| placeholders | array | Yes | Bounded distinct placeholder receipts with name, occurrences, and first span. The item shape is defined by the nested schema and the array contains from 0 through 10000 items. |
| summary | object | Yes | Closed placeholder and issue count summary. Its documented communication properties are bounded to 256 entries — unconstrained RFC 8259 JSON items. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"communication.template_validate","input":{"template":"Hi {{first_name}}, your order {{order_id}} ships on {{ship_date}}.","allowedVariables":["first_name","order_id","ship_date"],"syntax":"MUSTACHE"}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("communication.template_validate", {"allowedVariables":["first_name","order_id","ship_date","tracking_url"],"requiredVariables":["order_id"],"template":"Hi {{first_name}}, your order {{order_id}} ships on {{ship_date}}. Thanks again, {{first_name}}.","syntax":"MUSTACHE"});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| TEMPLATE_INVALID | 400 | No | The request payload failed this skill's input validation. |
| TEMPLATE_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
| TEMPLATE_PLACEHOLDER_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| TEMPLATE_SYNTAX_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| TEMPLATE_ALLOWLIST_INVALID | 400 | No | The request payload failed this skill's input validation. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not send, queue or deliver anything, and opens no connection of any kind
- Does not render the template — communication.template_merge does that
- Does not evaluate a section, a loop, a filter or a conditional; those are reported as unsupported forms rather than interpreted
- Does not check spelling, grammar or tone
- Does not decide whether a name is sensitive; the allowlist you supply is the whole of the policy
- Does not repair a template or hand back a corrected one
- Rendering a template, which is a different skill
- Templates whose control syntax you actually need evaluated
- Deciding on your behalf which names are safe to expose
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "communication.template_validate",
"input": {
"allowedVariables": [
"first_name",
"order_id",
"ship_date",
"tracking_url"
],
"requiredVariables": [
"order_id"
],
"template": "Hi {{first_name}}, your order {{order_id}} ships on {{ship_date}}. Thanks again, {{first_name}}.",
"syntax": "MUSTACHE"
}
}Examples
An approved template where every placeholder is on the allowlist
{
"allowedVariables": [
"first_name",
"order_id",
"ship_date",
"tracking_url"
],
"requiredVariables": [
"order_id"
],
"template": "Hi {{first_name}}, your order {{order_id}} ships on {{ship_date}}. Thanks again, {{first_name}}.",
"syntax": "MUSTACHE"
}{
"unknown": [],
"missing": [],
"unsupportedForms": [],
"valid": true,
"summary": {
"placeholderCount": 4,
"distinctPlaceholderCount": 3,
"truncated": false,
"missingCount": 0,
"syntax": "MUSTACHE",
"unknownCount": 0
},
"placeholders": [
{
"name": "first_name",
"occurrences": 2,
"allowed": true
},
{
"name": "order_id",
"occurrences": 1,
"allowed": true
},
{
"name": "ship_date",
"occurrences": 1,
"allowed": true
}
]
}A template carrying a forbidden field, a section and a missing requirement
{
"wildcards": true,
"requiredVariables": [
"unsubscribe_url"
],
"template": "Hi {{first_name}}, {{#if premium}}your priority order{{/if}} {{order_id}} ships to {{customer.address.city}}. Card on file: {{customer.card_number}}.",
"allowedVariables": [
"first_name",
"order_id",
"customer.*"
],
"syntax": "MUSTACHE"
}{
"unknown": [],
"missing": [
"unsubscribe_url"
],
"unsupportedForms": [
{
"reason": "CONTROL_TAG",
"form": "{{#if premium}}",
"span": {
"start": 19,
"end": 34
}
},
{
"reason": "CONTROL_TAG",
"form": "{{/if}}",
"span": {
"start": 53,
"end": 60
}
}
],
"valid": false,
"summary": {
"placeholderCount": 4,
"distinctPlaceholderCount": 4,
"truncated": false,
"missingCount": 1,
"syntax": "MUSTACHE",
"unknownCount": 0
},
"placeholders": [
{
"name": "first_name",
"occurrences": 1,
"allowed": true
},
{
"name": "order_id",
"occurrences": 1,
"allowed": true
},
{
"name": "customer.address.city",
"occurrences": 1,
"allowed": true
},
{
"name": "customer.card_number",
"occurrences": 1,
"allowed": true
}
]
}Recommended use cases
- Gate a template in review, so a placeholder nobody approved cannot reach a customer-facing message
- Stop a template from referencing a field your data-protection policy forbids, by keeping that field off the allowlist
- Fail a build when a transactional template loses its unsubscribe link or its order number
- Take an inventory of what a template needs before you go and assemble the data
Avoid for
- Rendering a template, which is a different skill
- Templates whose control syntax you actually need evaluated
- Deciding on your behalf which names are safe to expose
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.