Skip to Content
splashify CLITemplates (WA + RCS)

Templates

The templates / template commands manage WhatsApp templates (mirroring /templates and /templates/create), and the rcs templates / rcs template commands manage RCS templates (mirroring /templates/rcs/create).

Both surfaces share the same lifecycle:

You write a template ────► Submit to Meta / JioCX ────► Approval ────► Send to users ↑ (CLI handles this) ↑ (out-of-band)

The CLI handles the submit step and the read-side lifecycle (list, inspect, sync the status, delete). Approval is decided by Meta (WhatsApp) or JioCX (RCS) and surfaces in the status field — you poll it via the sync / check-status commands.

Backed by /api/v1/app/templates/* and /api/v1/app/rcs/templates/*.

Quick start — WhatsApp

# Simplest template: text-only marketing message splashify templates create \ --name "promo_welcome" \ --language en \ --category MARKETING \ --text "Welcome to our store! Use code WELCOME10 for 10% off your first order." # See it (status will be PENDING until Meta reviews it) splashify templates # list splashify template <template_id> # show one # Poll for the latest approval status splashify templates sync # sync ALL from Meta splashify templates sync <template_id> # sync just one

Quick start — RCS

# Simplest: basic text RCS template splashify rcs templates create \ --name "rcs_promo_welcome" \ --type basic \ --text "Welcome! Tap to shop our latest drop." splashify rcs templates # list splashify rcs template <template_id> # show one splashify rcs template <template_id> check-status # poll approval state

WhatsApp template commands

splashify templates — list

splashify templates # default splashify templates list # alias
Backed byGET /api/v1/app/templates

Response shape (per template):

{ "template_id": "1234567890123456", "waba_id": "11122233344", "name": "promo_welcome", "status": "APPROVED", "category": "MARKETING", "language": "en", "quality_rating": "GREEN", "components": [/* Meta components array */], "synced_at": "2026-05-21T09:14:22Z", "created_at": "2026-05-20T08:00:00Z" }

status will be one of APPROVED, PENDING, REJECTED, IN_APPEAL, or DISABLED — same values Meta uses.

splashify template <template_id> — show one

splashify template 1234567890123456

No dedicated backend GET — the CLI fetches the list and filters client-side. Same pattern as splashify member <id>, splashify canned <id>, etc.

splashify templates sync — pull from Meta

splashify templates sync # sync ALL templates (alias: refresh) splashify templates sync 1234567890123456 # sync just one template
Backed byPOST /api/v1/app/templates/sync
OrPOST /api/v1/app/templates/:template_id/sync

This is the canonical way to refresh template status (APPROVED / REJECTED / etc.) after Meta reviews it. Run it after a create, or on a cron to keep your local mirror in sync.

splashify templates upload-media <file> — get a media handle

For templates that have a media header (IMAGE / VIDEO / DOCUMENT), Meta requires you to upload the sample file first and reference its media handle in the components array. The CLI exposes this as a two-step flow:

# 1. Upload — get back a handle HANDLE=$(splashify templates upload-media ./hero.jpg | jq -r '.handle') echo "$HANDLE" # "4::aW1hZ2UvanBlZw==:..." (opaque) # 2. Plug it into example.header_handle in your components cat <<EOF > ./tpl-with-media.json { "name": "spring_promo", "language": "en", "category": "MARKETING", "components": [ { "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["$HANDLE"] } }, { "type": "BODY", "text": "Hi {{1}}, check out our spring drop ↓" } ] } EOF splashify templates create --file ./tpl-with-media.json
Backed byPOST /api/v1/app/templates/media-handle (multipart)

The handle is persistent across templates for the lifetime Meta keeps it — you can reference one upload from multiple templates without re-uploading.

splashify templates create — submit a new template

Three ways to supply the body — pick whichever matches your shape.

# (1) Convenience for a TEXT-only template splashify templates create \ --name "promo_welcome" --language en --category MARKETING \ --text "Welcome to our store!" # (2) Inline components JSON splashify templates create \ --name "promo_welcome" --language en --category MARKETING \ --components '[ {"type":"BODY","text":"Hi {{1}}, your order {{2}} has shipped."}, {"type":"FOOTER","text":"Reply STOP to opt out"} ]' # (3) Full payload from disk — name/language/category live inside the file splashify templates create --file ./tpl-with-media.json
Backed byPOST /api/v1/app/templates
FlagRequiredNotes
--nameyesSnake_case recommended; Meta lowercases. Max 512 chars
--languageyesBCP-47 code: en, en_US, hi, es, …
--categoryyesMARKETING | UTILITY | AUTHENTICATION
--textone ofBody text — emits a single BODY component
--componentsone ofExplicit Meta components array (JSON)
--fileone ofPath to JSON with either a components array OR a full {name,language,category,components} payload

Components reference

A template is a list of Meta components. The full grammar (from the Meta docs ):

[ { "type": "HEADER", "format": "TEXT|IMAGE|VIDEO|DOCUMENT|LOCATION", "text": "…", // when format=TEXT "example": { "header_handle": ["<handle>"] // when format=IMAGE/VIDEO/DOCUMENT } }, { "type": "BODY", "text": "Hi {{1}}, your order {{2}} has shipped", "example": { "body_text": [["Alice", "ORD-1024"]] } }, { "type": "FOOTER", "text": "Reply STOP to opt out" }, { "type": "BUTTONS", "buttons": [ {"type": "QUICK_REPLY", "text": "Track order"}, {"type": "URL", "text": "Visit site", "url": "https://example.com/"}, {"type": "PHONE_NUMBER", "text": "Call us", "phone_number": "+919876543210"} ] } ]

Other supported component types: CAROUSEL (with nested cards[] → each card has its own HEADER/BODY/BUTTONS), LIMITED_TIME_OFFER (LTO), AUTHENTICATION body (with add_security_recommendation and code_expiration_minutes).

Variables. {{1}}, {{2}}, … are positional placeholders Meta fills in at send time. Each variable requires a sample value in example.body_text for Meta’s review.

splashify templates delete <template_id> — remove

splashify templates delete 1234567890123456 splashify templates delete 1234567890123456 --name "promo_welcome" # safer
Backed byDELETE /api/v1/app/templates/:template_id[?name=…]

Optionally pass --name so Meta can target the deletion accurately by name — recommended for templates with multiple language variants under the same name.

RCS template commands

RCS has its own provider stack (JioCX for India) and its own template shape. The rcs templates subtree namespaces these to keep them distinct from WhatsApp’s.

Feature gate. RCS templates require the RCS feature to be enabled on your account. If not, the create endpoint returns HTTP 403 with RCS is not enabled for your account — contact your provider to enable it.

splashify rcs templates — list

splashify rcs templates # default splashify rcs templates list # alias splashify rcs template <id> # show one
Backed byGET /api/v1/app/rcs/templates

Response shape (per template):

{ "template_id": "uuid", "name": "rcs_promo_welcome", "template_type": "rich_card", // basic | rich_card | carousel "status": "approved", // pending | ai_reviewing | approved | rejected "template_data": "{\"type\":\"card\",\"card\":{…}}", "rejection_reason": "", "created_at": "...", "updated_at": "..." }

template_data is stored as a JSON-encoded string — use jq -r '.template_data | fromjson' to expand it.

splashify rcs template <id> check-status — poll approval

splashify rcs template <template_id> check-status splashify rcs template <template_id> status # alias
Backed byGET /api/v1/app/rcs/templates/:template_id/check-status

Returns the latest status from the underlying RCS provider. Run it after a create to confirm pendingapproved.

splashify rcs templates upload-media — upload rich-card media

splashify rcs templates upload-media ./hero.jpg --height MEDIUM splashify rcs templates upload-media ./tall.jpg --height TALL splashify rcs templates upload-media ./short.jpg --height SHORT
Backed byPOST /api/v1/app/rcs/templates/upload-media (multipart with media_height field)

Returns a URL you plug into a rich_card’s file_url (and optionally thumbnail_url).

--heightMedia slot proportions
SHORTaspect ratio ~16:9, ~108dp
MEDIUM~16:9, ~168dp (default)
TALL~16:9, ~264dp

splashify rcs templates create — submit

Three ways to supply template_data:

# (1) Convenience — basic text RCS message splashify rcs templates create \ --name "rcs_promo_welcome" \ --type basic \ --text "Welcome! Tap to shop our latest drop." # (2) Inline JSON for a rich card or carousel splashify rcs templates create \ --name "rcs_card" \ --type rich_card \ --data '{ "type": "card", "card": { "title": "Spring drop", "description": "20% off all jackets — this week only", "url": "<file_url from upload-media>", "suggestions": [ {"type": "url", "text": "Shop now", "url": "https://example.com/spring", "postback": "shop_now"}, {"type": "message", "text": "Notify me", "postback": "notify_me"} ] } }' # (3) Full payload from disk splashify rcs templates create --name "rcs_carousel" --type carousel --file ./carousel.json
Backed byPOST /api/v1/app/rcs/templates
FlagRequiredNotes
--nameyesDisplay name
--typeyesbasic | rich_card | carousel
--textone ofBody text (basic only) — emits {"type":"text","text":"…"}
--dataone ofInline JSON template_data
--fileone ofPath to a JSON file with template_data

template_data shapes

The shapes the app dialog builds (see buildTemplateData):

// type: basic { "type": "text", "text": "Hi! Tap to shop ↓", "suggestions": [/* optional */] } // type: rich_card { "type": "card", "card": { "title": "Spring drop", "description": "20% off…", "url": "<media file_url>", "thumbnail_url": "<optional>", "suggestions": [/* optional */] } } // type: carousel { "type": "multiple_cards", "cards": [ { "card": { /* same shape as rich_card.card */ } }, { "card": { /* … */ } } ] }

Suggestion shapes

Five suggestion types are supported (mapped from the page’s form to RML on submit):

// reply → quick-reply chip {"type": "message", "text": "Notify me", "postback": "notify_me"} // open URL {"type": "url", "text": "Shop now", "url": "https://example.com/", "postback": "shop_now"} // dialer {"type": "dial", "text": "Call us", "call_to": "+919876543210", "postback": "call_us"} // calendar event { "type": "calendar", "text": "Add to calendar", "postback": "cal", "title": "Brand launch", "description": "RSVP early", "start": "2026-06-01T10:00:00Z", "end": "2026-06-01T12:00:00Z" } // location { "type": "location", "text": "Find a store", "postback": "loc", "latitude": 12.9716, "longitude": 77.5946 }

splashify rcs templates delete

splashify rcs templates delete <template_id>
Backed byDELETE /api/v1/app/rcs/templates/:template_id

Common workflows

Bulk-sync once a day via cron

# crontab — every night at 03:30 30 3 * * * /usr/local/bin/splashify templates sync >/var/log/splashify-tpl-sync.log 2>&1

The endpoint is idempotent. Each run upserts the local mirror with Meta’s authoritative status, so a rejected template visibly flips within ~24h.

Submit a template and wait for approval

# Submit RESP=$(splashify templates create --name "promo" --language en --category MARKETING --text "Hi!") TID=$(echo "$RESP" | jq -r '.template_id // .id') # Poll every 30s for up to 10 minutes for _ in $(seq 1 20); do sleep 30 splashify templates sync "$TID" > /dev/null STATUS=$(splashify template "$TID" | jq -r '.status') echo "$STATUS" [ "$STATUS" = "APPROVED" ] && break [ "$STATUS" = "REJECTED" ] && break done

Find every REJECTED template + reason

splashify templates | jq '.templates[] | select(.status == "REJECTED") | {template_id, name, language, quality_rating}'

Export a template’s components as a re-usable JSON file

splashify template <id> | jq '{name, language, category, components}' > export.json # Submit it to another WABA (after you switch your CLI's context) with: splashify templates create --file ./export.json
// ./carousel.json { "name": "spring_carousel", "language": "en", "category": "MARKETING", "components": [ { "type": "BODY", "text": "Spring drop — pick your style ↓" }, { "type": "CAROUSEL", "cards": [ { "components": [ { "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["<handle-1>"] } }, { "type": "BODY", "text": "Jackets — 20% off" }, { "type": "BUTTONS","buttons": [{"type":"URL","text":"Shop","url":"https://example.com/jackets"}]} ] }, { "components": [ { "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["<handle-2>"] } }, { "type": "BODY", "text": "Tees — 15% off" }, { "type": "BUTTONS","buttons": [{"type":"URL","text":"Shop","url":"https://example.com/tees"}]} ] } ] } ] }
splashify templates create --file ./carousel.json
// ./rcs-carousel.json { "type": "multiple_cards", "cards": [ { "card": { "title": "Jackets", "description": "20% off", "url": "<file_url-1>", "suggestions": [ {"type": "url", "text": "Shop", "url": "https://example.com/jackets", "postback": "shop_jackets"} ] } }, { "card": { "title": "Tees", "description": "15% off", "url": "<file_url-2>", "suggestions": [ {"type": "url", "text": "Shop", "url": "https://example.com/tees", "postback": "shop_tees"} ] } } ] }
splashify rcs templates create --name "rcs_spring_carousel" --type carousel --file ./rcs-carousel.json

Troubleshooting

WhatsApp

“No WABA connected to your account yet” — finish the Meta Embedded Signup flow at app.splashifypro.com → WhatsApp → Connect Number, then retry. splashify waba confirms the connection.

templates create returns Meta’s error_user_msg — Meta refused the template. The most common reasons: missing variables in the body ({{1}} etc. without example.body_text), category mismatch (promotional language in a UTILITY template), or restricted content. The CLI surfaces Meta’s error_user_msg directly so the message is actionable.

Status stuck at PENDING — Meta reviews can take up to 24h (usually < 2 minutes). Run splashify templates sync <template_id> to force-refresh; if it’s still PENDING after an hour, Meta is backed up — wait.

upload-media returns an opaque error — file too large (Meta caps templates at 5MB image / 16MB video / 100MB document), unsupported format (JPG/PNG/MP4/PDF/DOCX/XLSX), or the file path is wrong. Try a smaller / simpler file first.

RCS

“RCS is not enabled for your account” — RCS is a separately-billed feature. Check splashify subscription and contact your provider to add the RCS add-on.

Status ai_reviewing — the provider runs an AI policy check first. It usually resolves to approved or rejected within seconds. rcs template <id> check-status re-polls.

rejected with rejection_reason — read the reason field for the specific guideline that was violated. Fix the template_data and re-create (RCS templates cannot be edited in place; create a new one).

Carousel only has 1 card — RCS requires ≥ 2 cards in a carousel. The page enforces this client-side; the backend may also reject it.

See also