Skip to content
Splashify Pro
Docs

splashify CLI Commands

The splashify CLI controls your Splashify Pro account from the command line. Every command authenticates with your stored oc_live_ access token and prints the backend's JSON response.

The CLI performs every app task directly — sending messages, managing contacts, running broadcasts, and more. It can also install the Splashify OpenClaw skill for you, but that is optional: the CLI is fully usable on its own.

Setup commands

Configuration is stored at ~/.splashify/config.json (mode 0600).

Access token commands

See Access Tokens for details.

OpenClaw skill command

splashify link openclaw writes the skill bundle (embedded in the CLI binary) into ~/.openclaw/workspace/skills/splashify/. Use --print to preview the target path, or --path to override it. This is the only OpenClaw-related command — there is no MCP server to configure. See the OpenClaw skill section for the full integration guide.

Messaging commands

bash
# Send a free-form text message (within the 24-hour window)
splashify message send --to +919876543210 --text "Your order has shipped"

# Send an approved WhatsApp template
splashify message template --to +919876543210 --name order_update \
  --lang en --vars '["John","ORD-1024"]'

# Send a media message
splashify message media --to +919876543210 --type image \
  --url https://example.com/receipt.png --caption "Receipt"

# Conversations
splashify conversations --status open        # list chats
splashify conversation <id>                  # read one chat
splashify conversation <id> resolve          # close a chat
splashify unread                             # unread message count

Contact commands

bash
splashify contacts --search john --tag vip
splashify contact <id>
splashify contact create --phone +919876543210 --name "John Roe" --email [email protected]
splashify contact update <id> --notes "VIP. Prefers WhatsApp."
splashify contact tag   <id> --tags vip,lead
splashify contact untag <id> --tags lead
splashify contact block <id>
splashify contact unblock <id>
splashify contact delete <id>

Full guide: Contacts — covers sparse-PUT update semantics, tag/untag vs. update, block/unblock side-effects, and bulk patterns.

Account commands (read-only)

The account command mirrors everything the Settings → Account Details page shows in the app — profile, plan, wallet balance, WhatsApp identifiers, the organizations you belong to, and your invitations (received and sent). Available since v0.1.4.

Read-only by design. None of these subcommands accept, reject, switch, invite, or update anything. To change account state, use the web app.

bash
splashify account                            # consolidated view (all sections at once)
splashify account info                       # profile + plan (from /app/me)
splashify account orgs                       # organizations you belong to
splashify account invitations                # pending invitations received
splashify account sent-invitations           # invitations you have sent
splashify account wallet                     # wallet balance

The default consolidated view returns:

json
{
  "user": { ... profile, plan, dates ... },
  "wallet": { "wallet_amount": 1234.56, ... },
  "whatsapp": { "phone_number": "+91...", "waba_id": "...", ... },
  "organizations": { "organizations": [ ... ] },
  "invitations_received": { "invitations": [ ... ] },
  "invitations_sent": { "invitations": [ ... ] }
}

A section that fails for any reason is reported as {"error": "..."} for that key — the command never aborts the whole view because one part is unavailable.

Common one-liners with jq:

bash
# Just the things that matter day-to-day
splashify account | jq '{
  email:        .user.user.email,
  plan:         .user.user.plan_name,
  plan_status:  .user.user.plan_status,
  expires:      .user.user.plan_expires_at,
  wallet:       .wallet.wallet_amount,
  waba_phone:   .whatsapp.phone_number,
  org_count:    (.organizations.organizations | length),
  pending_invs: (.invitations_received.invitations | length)
}'

# Org IDs only
splashify account orgs | jq -r '.organizations[].org_user_id'

# Pending invitations summary
splashify account invitations | jq '.invitations[] | {from: .inviter_org, role, expires: .expires_at}'

Support tickets

Full mirror of /support and /support/<ticket_id> — list and filter your tickets, file a new one, follow up with a reply, or close it. Available since v0.1.25. No subscription / balance preflight — support is free for every account.

bash
splashify support                                       # list every ticket
splashify support list --status open                    # status filter
splashify support list --search "payment"               # subject substring
splashify tickets                                       # alias for support

splashify ticket <id>                                   # detail with replies
splashify ticket create \
  --title "Cannot send broadcast to segment X" \
  --description "When I try to send to segment X I get a 402…" \
  --category bug \
  --priority high

splashify ticket <id> reply "Thanks — the workaround worked, please close."
splashify ticket <id> close

Categories and priorities (match the page's dialogs)

Statuses you'll see (read-only)

Reply syntax shortcut

reply joins everything after it with spaces, so you don't have to quote a short message:

bash
splashify ticket abc reply Thanks for the update
# is equivalent to
splashify ticket abc reply "Thanks for the update"

For multi-line replies, pass an explicit quoted string with \n and let the shell expand it (or echo into a heredoc piped into splashify api).

Common patterns

bash
# Tickets that need a reply from me (the violet "Reply needed" pills)
splashify support list --status waiting_for_user | jq '.tickets[]'

# Open + in-progress count
splashify support | jq '.tickets | group_by(.status) | map({(.[0].status): length}) | add'

# File a quick bug report with subject + body
splashify ticket create \
  --title "Broadcast 405de606 stuck in 'sending'" \
  --description "Status hasn't changed for 30 minutes. cohort-counts shows 12 failed and 0 sent." \
  --category bug --priority high

# Latest unresolved ticket
splashify support | \
  jq '.tickets | map(select(.status != "resolved" and .status != "closed")) |
      sort_by(.updated_at) | reverse | .[0]'

# Bulk-close every resolved ticket older than 14 days
splashify support list --status resolved | \
  jq -r --arg cutoff "$(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
     '.tickets[] | select(.updated_at < $cutoff) | .ticket_id' | \
  xargs -I{} splashify ticket {} close

Track expenses

Full mirror of /settings/track-expenses — message-deduction summary, daily chart series, and the per-message transaction log, including the page's Export CSV button. The page is read-only; the CLI is too — no writes, no balance preflight. Available since v0.1.26.

bash
splashify expenses                                    # summary, last 30 days
splashify expenses summary --period 7d                # summary, last 7 days
splashify expenses categories --period 30d            # category breakdown only
splashify expenses countries  --period all            # top countries only
splashify expenses trends     --period 30d            # daily deduction series
splashify expenses logs       --period 30d --limit 200
splashify expenses logs       --category marketing --country IN --free-trial false
splashify expenses export     --period all --out spend.csv

--period values (same five the page exposes)

What summary returns

logs filters (applied client-side)

The list endpoint takes only period and limit, so additional filters match the page's UX by filtering the returned page. If your account exceeds 500 deductions in a period, raise --limit 500 (the backend max) or narrow --period.

CSV export — same columns as the page's button

splashify expenses export produces a UTF-8 BOM CSV (so Excel renders ₹ correctly) with the seven columns shown by the page's Export CSV button — no internal base-amount/markup-percent breakdown, just the customer-facing total:

text
Date, Recipient, Category, Country, Total Deducted, Trigger, Free Trial

Default output is stdout; pass --out file.csv to write to disk.

Common patterns

bash
# Total spend last 7 days
splashify expenses summary --period 7d | jq '.summary.total_deducted'

# Spend by category, last 30 days
splashify expenses categories | \
  jq '.summary | {marketing: .marketing_amount, utility: .utility_amount,
                  auth: .auth_amount, rcs: .rcs_amount, call: .call_amount}'

# Highest-spend country this period
splashify expenses countries --period 30d | jq '.top_countries[0]'

# Day with biggest spend in the last 90 days
splashify expenses trends --period 3m | jq '.data | max_by(.amount)'

# Just the authentication-template deductions to Indian numbers
splashify expenses logs --category authentication --country IN --limit 500

# Free-trial messages that didn't actually deduct anything
splashify expenses logs --free-trial true | \
  jq '.logs | map(select(.total_amount == 0)) | length'

# Monthly accounting export
splashify expenses export --period 30d --out "spend-$(date +%Y-%m).csv"

Calling

Full mirror of /calling — call history + analytics, "tap-to-call" and permission templates, backend-side dial, recording uploads, and the calling settings (business hours, call-icon visibility, etc.). Available since v0.1.24.

WebRTC live-audio is not exposed. The page's in-browser call dialog (/app/whatsapp-calling/*) requires real WebRTC SDP/ICE negotiation — a shell can't drive that. The CLI handles every REST-friendly piece: backend-side dial, template messages that trigger calls, history, analytics, permissions, settings, recording upload.

Subscription + balance preflight

Same preflight as broadcasts. Every command that triggers a charge runs:

  1. GET /app/developer/cli-eligibility → refuses with the documented subscription_expired / no_waba / account_suspended messages.
  2. GET /app/wallet/info → prints balance; refuses when wallet_amount ≤ 0 on commands that initiate a call or send a paid template.

Preflighted commands: call initiate, calling send call-button, calling send permission, calling send template, calling send permission-template. --yes skips the soft balance check; SPLASHIFY_SKIP_PREFLIGHT=1 skips both. Backend remains the source of truth.

Read — overview, calls, analytics, permissions, templates

bash
splashify calling                                          # overview (default)
splashify calling overview                                 # same

splashify calling settings                                 # GET calling settings
splashify calling analytics --start-time 2026-05-01T00:00:00Z \
                            --end-time   2026-05-20T00:00:00Z \
                            --granularity DAILY --dimensions DIRECTION

splashify calling calls                                    # list call history
splashify calling calls --status missed --page 1 --limit 50
splashify calling calls --search "+91987" --agent <agent_id>

splashify call <call_id>                                   # one call detail

splashify calling permission-status --phone "+919876543210"
splashify calling permissions --status granted --agent <agent_id>

splashify calling templates                                # list call templates
splashify calling template status --name "call_invitation"

Write — initiate, templates, sends, settings update, recording upload

bash
# Backend-side dial (no browser needed — backend places the call via Meta)
splashify call initiate --to "+919876543210"

# Upload a recording for a previous call (multipart)
splashify call upload-recording <call_id> ./recording.webm

# Create a "tap to call" template (template containing a call button)
splashify calling template create-call-button \
  --name "support_call_invite" \
  --body-text "Need help? Tap below to call our support team." \
  --button-label "Call us" \
  --phone-number "+919876543210" \
  --language en

# Create a permission-request template
splashify calling template create-permission \
  --name "ask_permission_to_call" \
  --body-text "May we call you about your order?" \
  --language en

# Make a template the default
splashify calling template set-default <template_id>

# Send a tap-to-call interactive message
splashify calling send call-button \
  --to "+919876543210" \
  --body-text "Want to talk to a human?" \
  --button-label "Call us" \
  --phone-number "+919876543210"

# Send a call permission request
splashify calling send permission \
  --to "+919876543210" \
  --body-text "May we call you about your order?"

# Send a permission request as a template (out-of-window safe)
splashify calling send permission --to "+919876543210" \
  --type template --template '{"name":"ask_permission_to_call","language":{"code":"en"}}'

# Send a call-button template (out-of-window safe)
splashify calling send template --to "+919876543210" \
  --name "support_call_invite" --language en --vars '["Alice"]'

# Send a permission-template
splashify calling send permission-template \
  --to "+919876543210" \
  --name "ask_permission_to_call" --language en

# Update calling settings (raw JSON; CLI wraps it as {"calling": <your_json>})
splashify calling settings update --data '{
  "calling_enabled": true,
  "business_hours_enabled": true,
  "call_hours": {
    "status": "ENABLED",
    "timezone_id": "Asia/Kolkata",
    "weekly_operating_hours": [
      {"day_of_week":"MONDAY","open_time":"09:00","close_time":"18:00"},
      {"day_of_week":"FRIDAY","open_time":"09:00","close_time":"18:00"}
    ],
    "holiday_schedule": []
  }
}'

What preflight failure looks like

text
$ splashify call initiate --to "+919876543210"
Wallet balance: ₹0.00
error: calls are unavailable — your wallet balance is ₹0.00.

  Recharge: https://app.splashifypro.com/dashboard
  (Or run with --yes to skip this soft-check; the backend still enforces it per-message.)

And for an expired subscription:

text
error: calls are unavailable — your trial has ended and there is no active paid plan on this account.

  Upgrade your plan:  https://app.splashifypro.com/settings/subscriptions

  Once a plan is active, retry the command.

End-to-end recipe — "tap-to-call" outreach

bash
# 1. Verify subscription + balance
splashify subscription | jq '.eligibility'
splashify account wallet | jq '.wallet_amount'

# 2. Create a call-button template (Meta must approve it before send)
splashify calling template create-call-button \
  --name "support_call_invite" \
  --body-text "Hi {{1}}, need help? Tap below to call our support team." \
  --button-label "Call us" \
  --phone-number "+919876543210" \
  --language en

# 3. Wait for approval, then check status
splashify calling template status --name support_call_invite

# 4. Once APPROVED, send to a customer
splashify calling send template \
  --to "+919876543210" \
  --name "support_call_invite" \
  --language en \
  --vars '["Aditya"]'

# 5. Customer taps "Call us" → backend logs the call
splashify calling calls --status completed --limit 5

Common patterns

bash
# Today's missed calls
splashify calling calls --status missed --limit 100 | \
  jq '.calls[] | select(.created_at | startswith("2026-05-20"))'

# Bulk send a call invite to a list of phone numbers
while read phone; do
  splashify calling send template --to "$phone" \
    --name support_call_invite --language en --vars "[\"$phone\"]"
done < numbers.txt

# Permission status check for a batch
while read phone; do
  status=$(splashify calling permission-status --phone "$phone" | jq -r '.status // "unknown"')
  echo "$phone	$status"
done < numbers.txt > permissions.tsv

# Backup call history as JSON
splashify calling calls --limit 1000 | jq '.calls' > call_history.json

Broadcasts

Full mirror of /broadcasts (the list + stats) and /broadcasts/<id> (detail, per-message status, cohort counts, export, cancel / restart / send-now / rebroadcast). Rebuilt in v0.1.23 — earlier versions had a broken stub that used the wrong JSON field names.

Subscription + balance preflight

Every command that triggers actual sends runs two checks first:

  1. Subscription — GET /api/v1/app/developer/cli-eligibility. Refuses with the existing subscription_expired / no_waba / account_suspended messages (same as splashify connect).
  2. Wallet balance — GET /api/v1/app/wallet/info. Prints the current balance; if it's ≤ 0 the command refuses with a recharge prompt.

Commands gated by the preflight: broadcast create, broadcast <id> send-now, broadcast <id> restart, broadcast <id> rebroadcast. Read commands and broadcast <id> cancel are not balance-gated.

Escape hatches:

  • --yes on create / rebroadcast skips the balance soft-check.
  • SPLASHIFY_SKIP_PREFLIGHT=1 skips both checks.
  • The backend still enforces per-message via wallet_billing_service.CheckBalance — preflight is for fast, friendly UX, not security.

Read

bash
splashify broadcasts                                    # list
splashify broadcasts --status sending --page 2 --limit 50
splashify broadcasts --search "May launch"

splashify broadcast stats                               # global stats
splashify broadcast stats --period 30d                  # 7d / 30d / 90d / all

splashify broadcast <id>                                # detail
splashify broadcast <id> --recompute                    # ask backend to recompute counts
splashify broadcast <id> messages                       # per-message status (paginated)
splashify broadcast <id> messages --status FAILED --page 1 --limit 100
splashify broadcast <id> messages --channel rcs --cumulative true
splashify broadcast <id> cohorts                        # cohort-counts (failed/sent/delivered/read)
splashify broadcast <id> export --status ALL            # JSON export
splashify broadcast <id> export --status FAILED --csv   # CSV (binary; pipe to a file)

Write

bash
# Create — WhatsApp template broadcast to a segment
splashify broadcast create \
  --name "May launch" \
  --template promo_welcome \
  --category MARKETING \
  --language en \
  --segment-ids "<seg_id_1>,<seg_id_2>"

# With template variables and a header image
splashify broadcast create \
  --name "Welcome wave" \
  --template welcome_with_image \
  --category MARKETING --language en \
  --params '{"components":[{"type":"body","parameters":[{"type":"text","text":"Alice"}]}]}' \
  --media-url https://example.com/header.jpg \
  --contact-ids "<cid_1>,<cid_2>,<cid_3>"

# Scheduled
splashify broadcast create \
  --name "June nudge" --template june_offer --category MARKETING --language en \
  --segment-ids "<seg_id>" \
  --send-type scheduled --scheduled-at 2026-06-01T10:00:00Z \
  --rate-limit 100 --batch-size 50

# Pure RCS
splashify broadcast create \
  --name "RCS launch" --template rcs_offer --category MARKETING --language en \
  --segment-ids "<seg_id>" --rcs

# WhatsApp with RCS fallback
splashify broadcast create \
  --name "WA→RCS" --template promo --category MARKETING --language en \
  --segment-ids "<seg_id>" \
  --rcs-fallback-template-id "<rcs_tpl_id>" --rcs-fallback-template-name rcs_promo

# Cancel / restart / send-now
splashify broadcast <id> cancel
splashify broadcast <id> restart
splashify broadcast <id> send-now

# Re-send to a cohort of an existing broadcast
splashify broadcast <id> rebroadcast --cohort failed
splashify broadcast <id> rebroadcast --cohort delivered --name "Follow-up" \
                                     --template promo_followup --send-at 2026-06-05T10:00:00Z

Required fields for create

What happens when preflight fails

text
$ splashify broadcast create --name "May launch" --template promo \
    --category MARKETING --language en --segment-ids "<seg>"
Wallet balance: ₹0.00
error: broadcasts are unavailable — your wallet balance is ₹0.00.

  Recharge: https://app.splashifypro.com/dashboard
  (Or run with --yes to skip this soft-check; the backend still enforces it per-message.)

For a subscription-expired account:

text
error: broadcasts are unavailable — your trial has ended and there is no
active paid plan on this account.

  Upgrade your plan:  https://app.splashifypro.com/settings/subscriptions

  Once a plan is active, retry the command.

If the backend rejects mid-send (e.g. balance drains during the broadcast), the per-message error surfaces directly:

text
error: HTTP 402: Insufficient balance. Required: ₹0.95, Available: ₹0.20

End-to-end recipe

bash
# 1. Make sure you have an active subscription + non-zero balance
splashify subscription | jq '.eligibility'
splashify account wallet | jq '.wallet_amount'

# 2. Find a segment to target
SEG=$(splashify segments --search "VIP" | jq -r '.segments[0].id')

# 3. Create the broadcast (preflight runs automatically)
splashify broadcast create \
  --name "May VIP launch" \
  --template may_launch_v2 --category MARKETING --language en \
  --segment-ids "$SEG"

# 4. Grab the new broadcast id from the response
BCAST=$(splashify broadcasts --search "May VIP launch" | jq -r '.broadcasts[0].id')

# 5. Watch progress
splashify broadcast "$BCAST" --recompute | jq '{status, sent, delivered, failed}'
splashify broadcast "$BCAST" cohorts | jq

# 6. If some failed, re-target just those
splashify broadcast "$BCAST" rebroadcast --cohort failed

Common patterns

bash
# Find every broadcast that's still sending or scheduled
splashify broadcasts | jq -r '.broadcasts[] | select(.status == "sending" or .status == "scheduled") | .id'

# Bulk-cancel scheduled broadcasts
splashify broadcasts --status scheduled | \
  jq -r '.broadcasts[].id' | \
  xargs -I{} splashify broadcast {} cancel

# Export the FAILED cohort as CSV
splashify broadcast <id> export --status FAILED --csv > failed.csv

# Total wallet spend on broadcasts over 30 days (approximate — sum of message counts × per-message rate)
splashify broadcast stats --period 30d | jq '{period, sent_count, total_cost}'

Email marketing

The email command covers the full email-marketing surface — five pages and 30+ endpoints. Mirrors /email, /settings/email-domain, /email/templates, /email/audience, /email/campaigns. Available since v0.1.22.

Dashboard

bash
splashify email                              # dashboard stats (default)
splashify email stats                        # same

Backed by GET /api/v1/app/email/dashboard/stats.

Sender domains (/settings/email-domain)

Required before you can send anything — Splashify will reject campaigns whose from_email isn't on a verified domain.

bash
splashify email domains                      # list every domain + status
splashify email domain example.com           # one domain: DKIM/SPF/DMARC records + status
splashify email domain add example.com       # register a domain
splashify email domain verify example.com    # re-check DNS records
splashify email domain delete example.com    # remove

Templates (/email/templates)

Templates are JSON documents produced by the visual block editor (header / image / text / button / divider / spacer / footer + variables). The CLI doesn't model the editor — pass --file <path> to a JSON file or --data '{…}' inline.

bash
splashify email templates                          # list
splashify email template <id>                      # detail

# Create — design once in the web editor, save the template_json to disk
splashify email template create \
  --name "Welcome" \
  --subject "Welcome to Splashify Pro" \
  --file ./welcome-template.json

# Update — only the flags you pass change (read-modify-write)
splashify email template update <id> --subject "New subject line"
splashify email template update <id> --file ./welcome-template-v2.json

# Render the preview HTML for a template with variables filled in
splashify email template preview \
  --file ./welcome-template.json \
  --vars '{"first_name":"Alice","plan":"GROWTH"}'

splashify email template delete <id>

template update is read-modify-write because the backend's PUT requires {name, subject, template_json} — sending only --subject would otherwise blank the others. Same pattern as splashify waba update.

Audience (/email/audience)

Stats

bash
splashify email audience                     # stats (default)
splashify email audience stats               # same

GET /api/v1/app/email/audience/stats.

Contacts

bash
splashify email audience contacts                            # list
splashify email audience contacts --status subscribed
splashify email audience contacts --search "@acme.com"
splashify email audience contact <id>                        # detail
splashify email audience contact add \
  --emails "[email protected],[email protected],[email protected]" \
  --metadata '{"plan":"pro","source":"webinar"}' \
  --segments "<segment_id_1>,<segment_id_2>"
splashify email audience contact update <id> --status unsubscribed
splashify email audience contact update <id> --metadata '{"plan":"enterprise"}'
splashify email audience contact delete <id>

Segments

bash
splashify email audience segments                                # list
splashify email audience segment <id>                            # detail (list-and-filter)
splashify email audience segment create --name "VIP" --description "High-value contacts"
splashify email audience segment update <id> --name "VIP customers"
splashify email audience segment delete <id>
splashify email audience segment <id> add-contacts <cid1>,<cid2>,<cid3>
splashify email audience segment <id> remove-contacts <cid1>,<cid2>

Campaigns (/email/campaigns)

bash
splashify email campaigns                    # list
splashify email campaign <id>                # detail (status, stats, recipients)

# Create — recipients are either segments OR specific contact IDs (or both)
splashify email campaign create \
  --name "May launch" \
  --template-id <template_id> \
  --from-name "Acme" \
  --from-email "[email protected]" \
  --reply-to "[email protected]" \
  --segment-ids <segment_id_1>,<segment_id_2> \
  --scheduled-at 2026-06-01T10:00:00Z

# Or target specific contacts directly
splashify email campaign create \
  --name "Win-back" --template-id <id> \
  --from-name "Acme" --from-email "[email protected]" \
  --contact-ids <cid1>,<cid2>

# Send or cancel
splashify email campaign send <id>           # send now (or trigger scheduled send)
splashify email campaign cancel <id>         # cancel a scheduled / in-progress campaign

--from-email must be on a verified domain (splashify email domains → status: verified). If you skip --scheduled-at the campaign is created in draft and sent only when you run campaign send.

End-to-end recipe — design a campaign and ship it

bash
# 1. Verify your sender domain
splashify email domain add example.com
# (paste the returned DKIM/SPF records into your DNS, wait a minute)
splashify email domain verify example.com

# 2. Build the template in the web app once, then export it
splashify email template <existing_id> | jq '.template.template_json' > /tmp/welcome.json
splashify email template create --name "May welcome" --subject "Welcome 👋" --file /tmp/welcome.json

# 3. Build the audience
splashify email audience segment create --name "May launch"
SEG=$(splashify email audience segments | jq -r '.segments[] | select(.name == "May launch") | .segment_id')

splashify email audience contact add --emails "[email protected],[email protected]" --segments "$SEG"

# 4. Create the campaign and send
TPL=$(splashify email templates | jq -r '.templates[] | select(.name == "May welcome") | .template_id')
splashify email campaign create \
  --name "May welcome" --template-id "$TPL" \
  --from-name "Acme" --from-email "[email protected]" \
  --segment-ids "$SEG"

CAMP=$(splashify email campaigns | jq -r '.campaigns[] | select(.name == "May welcome") | .campaign_id')
splashify email campaign send "$CAMP"

# 5. Track delivery
splashify email campaign "$CAMP" | jq '{status, sent_count, delivered_count, opened_count, clicked_count}'

Common patterns

bash
# Pending-verification domains
splashify email domains | jq '.domains[] | select(.status != "verified")'

# Bulk-import contacts from a CSV column (one email per line)
xargs -I{} splashify email audience contact add --emails {} < emails.txt

# Pause everything in flight
splashify email campaigns | \
  jq -r '.campaigns[] | select(.status == "sending" or .status == "scheduled") | .campaign_id' | \
  xargs -I{} splashify email campaign cancel {}

# Find templates that haven't been used in any campaign
USED=$(splashify email campaigns | jq -r '.campaigns[].template_id' | sort -u)
splashify email templates | jq --argjson used "$(echo "$USED" | jq -R . | jq -s .)" \
  '.templates[] | select(.template_id as $id | $used | index($id) | not) | {template_id, name}'

The credits command mirrors the credit widgets on /dashboard — your Advertisement Credits balance plus the Voice AI widget's full state (per-minute rate, trial minutes remaining, available minutes, and the list of voice agents). Available since v0.1.21.

Read-only by design. Recharges, payment verification, and Razorpay flows stay in the web app. The CLI shows what's currently on the account.

bash
splashify credits                            # consolidated view (all sections)
splashify credits ai                         # Advertisement Credits balance (legacy name)
splashify credits transactions               # Advertisement Credits history
splashify credits voice                      # voice AI rate + balance + trial
splashify credits agents                     # voice AI agents

# Aliases
splashify credit              # singular
splashify ai-credits          # full word

Voice AI rate response

json
{
  "rate_per_minute":          0.50,
  "trial_credited":           true,
  "trial_minutes_remaining":  47,
  "ai_credit_balance":        123.45,
  "available_minutes":        246
}

available_minutes = ai_credit_balance ÷ rate_per_minute + any remaining trial_minutes_remaining. Use it to gauge when the next recharge will be needed.

Consolidated view shape

json
{
  "ai_credit_info":   { ... balance, lifetime spent, last recharge, ... },
  "voice_ai_rate":    { "rate_per_minute": ..., "ai_credit_balance": ..., ... },
  "voice_ai_agents":  { "agents": [ ... ] }
}

A section that fails for any reason is reported as {"error": "..."} for that key — the rest of the view still renders.

Common patterns

bash
# Just the headline numbers
splashify credits | jq '{
  ai_balance:        .ai_credit_info.balance,
  voice_balance:     .voice_ai_rate.ai_credit_balance,
  voice_rate:        .voice_ai_rate.rate_per_minute,
  available_minutes: .voice_ai_rate.available_minutes,
  trial_minutes:     .voice_ai_rate.trial_minutes_remaining,
  agent_count:       (.voice_ai_agents.agents | length)
}'

# Recent Advertisement Credits transactions
splashify credits transactions | jq '.transactions[] | {at: .created_at, amount, type, note}'

# Voice AI low-credit alarm (alert if < 30 minutes of runway)
MINUTES=$(splashify credits voice | jq '.available_minutes')
if [ "$MINUTES" -lt 30 ]; then
  echo "WARN: only $MINUTES voice-AI minutes left — recharge in app.splashifypro.com"
fi

# Which voice agents are active
splashify credits agents | jq -r '.agents[] | select(.is_active) | "\(.name)\t\(.status)"'

Activity logs (owner-only, read-only)

The activity command mirrors /activity-logs — the audit trail of every user and team action across your account (logins, contact edits, template changes, conversation events, tag mutations, and more). Owner-only on the backend; non-owner oc_live_ tokens get a 403 here. Available since v0.1.20.

bash
splashify activity                                  # latest 100 logs
splashify activity --limit 200                      # custom limit
splashify activity --action login                   # filter by action
splashify activity --entity contact                 # filter by entity type
splashify activity --entity contact --entity-id <id>
splashify activity --actor <user_id>                # filter by actor
splashify activity --search "john"                  # client-side text search

# Aliases
splashify activity-logs            # full word
splashify logs                     # short alias

Output shape

json
{
  "success": true,
  "logs": [
    {
      "log_id":      "uuid",
      "action":      "created",
      "entity_type": "contact",
      "entity_id":   "uuid",
      "entity_name": "Acme Corp",
      "actor_id":    "uuid",
      "actor_name":  "John Roe",
      "actor_email": "[email protected]",
      "actor_role":  "owner",
      "description": "John Roe created contact Acme Corp",
      "metadata":    "{...}",
      "created_at":  "2026-05-19T08:05:33.163Z"
    }
  ]
}

For entity_type: "auth" events, metadata is a JSON string with ip, city, region, country, org, timezone — pipe through jq to expand it.

Common patterns

bash
# Just the most recent 20 logins, with location info parsed
splashify activity --action login --limit 20 | \
  jq '.logs[] | {at: .created_at, who: .actor_email,
                 where: (.metadata | fromjson | "\(.city), \(.country)")}'

# Every action one user took in the last batch
splashify activity --actor <user_id> --limit 200 | \
  jq -r '.logs[] | "\(.created_at)\t\(.action)\t\(.entity_type)\t\(.entity_name)"'

# All deletions across the account
splashify activity --action deleted --limit 200 | jq

# Recent logins from outside India
splashify activity --action login --limit 100 | \
  jq '.logs[] | select(.metadata | length > 0)
              | .metadata | fromjson
              | select(.country != "IN")'

# Quick text search (mirrors the page's search box)
splashify activity --search "stop" | jq '.count, .logs[].description'

Attribute commands

The attribute / attributes commands mirror Settings → Attributes — custom columns added to each contact (Company, Job Title, Birthday, etc.). Full CRUD plus the page's visibility toggle and up/down reorder actions. Available since v0.1.12.

Read

bash
splashify attributes                              # list every attribute
splashify attributes --search co                  # client-side substring filter on label
splashify attribute <id>                          # show one (list-and-filter)

Write

bash
# Create a text attribute
splashify attribute create --label "Company" --type TEXT

# Create a required dropdown with options
splashify attribute create \
  --label "Lead Source" \
  --type SELECT \
  --options "Web,Referral,Ads,Trade Show" \
  --required true \
  --help "Where this lead came from"

# Multi-select
splashify attribute create --label "Interests" --type MULTISELECT \
  --options "Sales,Marketing,Support"

# Update — only flags you pass are changed (read-modify-write under the hood)
splashify attribute update <id> --label "Company Name"
splashify attribute update <id> --required false --help "Optional from now on"
splashify attribute update <id> --options "Web,Referral,Ads,Trade Show,Other"

# Toggle visibility on the contacts table
splashify attribute toggle-visibility <id>

# Move up or down in display order
splashify attribute reorder <id> up
splashify attribute reorder <id> down

# Delete (soft-delete; existing data on contacts is preserved per the page)
splashify attribute delete <id>

Endpoint reference

Attribute types

Why update is read-modify-write

The backend's PUT /api/v1/app/attributes/:id requires label + type to be present and writes every optional column (is_visible, is_required, options, default_value, help_text) on every call. The CLI loads the current row first, overlays only the flags you passed, then submits the full body so omitted fields stay intact. Same pattern as splashify waba update and splashify opt ….

Common patterns

bash
# IDs of all required attributes
splashify attributes | jq -r '.attributes[] | select(.is_required) | .id'

# Look up an attribute id by label
splashify attributes | jq -r '.attributes[] | select(.label == "Company") | .attribute_id'

# Hide every SELECT attribute from the contacts table (visibility toggle)
splashify attributes | jq -r '.attributes[] | select(.type == "SELECT" and .is_visible) | .attribute_id' | \
  xargs -I{} splashify attribute toggle-visibility {}

# Bulk delete attributes whose label starts with "test_"
splashify attributes | jq -r '.attributes[] | select(.label | startswith("test_")) | .attribute_id' | \
  xargs -I{} splashify attribute delete {}

Segment commands

segments / segment mirror Settings → Segments — the saved audience filters used to slice your contact list for broadcasts and reporting. Full CRUD plus per-segment introspection. Available since v0.1.11.

Read

bash
splashify segments                                # list, first page
splashify segments --page 2 --limit 50            # paginate
splashify segments --search vip                   # server-side name search
splashify segments stats                          # overall stats

splashify segment <id>                            # one segment
splashify segment <id> contacts                   # contacts in this segment
splashify segment <id> contacts --page 2 --limit 100
splashify segment <id> count                      # current member count
splashify segment <id> refresh                    # recompute count (for dynamic segments)

Write

bash
# Create — --filters JSON is required (see DSL below)
splashify segment create \
  --name "VIP iOS users" \
  --description "Tagged VIP, on iOS, marketing-opted-in" \
  --filters '{
    "conditions": [
      {"field": "tags",        "operator": "includes", "value": "VIP"},
      {"field": "hasOptedOut", "operator": "equals",   "value": false}
    ],
    "logic": "and"
  }' \
  --dynamic true \
  --active  true

# Update — every flag is optional; only what you pass is sent (PATCH semantics)
splashify segment update <id> --name "VIP — refreshed"
splashify segment update <id> --active false
splashify segment update <id> --filters '{"conditions":[…],"logic":"or"}'

# Delete
splashify segment delete <id>

Filter DSL reference

The --filters JSON is shipped to the backend verbatim. Shape:

json
{
  "conditions": [
    {"field": "<field>", "operator": "<operator>", "value": "<value>"},
    {"field": "<field>", "operator": "<operator>"}        // value omitted for isEmpty / isNotEmpty
  ],
  "logic": "and"   // or "or"
}

Fields (from CreateSegmentDialog):

Operators:

The web app (/settings/segments) is a visual builder for this DSL. Use it once to design a complex segment, then splashify segment <id> to see the JSON it produced, then build similar segments programmatically.

Common jq patterns

bash
# IDs of all active segments
splashify segments --limit 100 | jq -r '.segments[] | select(.is_active) | .id'

# Get a segment ID by exact name
splashify segments --search "VIP" | \
  jq -r '.segments[] | select(.name == "VIP") | .id'

# Use a segment ID in a broadcast (existing command)
SEGMENT_ID=$(splashify segments --search "VIP" | jq -r '.segments[0].id')
splashify broadcast create --name "May launch" \
  --template payment_failed \
  --audience-type segment --audience-id "$SEGMENT_ID"

# Trigger refresh on every dynamic segment (e.g. nightly cron)
splashify segments --limit 100 | \
  jq -r '.segments[] | select(.is_dynamic) | .id' | \
  xargs -I{} splashify segment {} refresh

Tag CRUD commands

The tag / tags commands mirror Settings → Tags — full CRUD on the tag library used to organise contacts. Available since v0.1.10.

bash
splashify tags                              # list every tag
splashify tags --search vip                 # client-side substring filter
splashify tag create "VIP"                  # create a tag
splashify tag create High Value Customer    # multi-word names work without quotes
splashify tag rename <id> "Important"       # rename a tag
splashify tag delete <id>                   # delete (unmaps from every contact)

Output shape

json
{
  "success": true,
  "tags": [
    {"id": "uuid", "name": "VIP"},
    {"id": "uuid", "name": "Newsletter"}
  ],
  "count": 2
}

Naming convention

The CLI uses plural for listing and singular for actions — the same pattern as contacts / contact, broadcasts / broadcast. There is no collision with contact tag <id> --tags … (that's the existing "tag a specific contact" command); the new tag group operates on tag entities themselves.

Deleting a tag is destructive

splashify tag delete <id> unmaps the tag from every contact that had it. The web app warns about this; the CLI does not prompt (it would break automation) — pipe through xargs if you want to script it, but double-check the IDs first:

bash
# Find which tags would be affected
splashify tags | jq -r '.tags[] | select(.name | test("^test-")) | "\(.id)\t\(.name)"'

# Bulk delete tags whose name starts with "test-" — irreversible
splashify tags | jq -r '.tags[] | select(.name | test("^test-")) | .id' | \
  xargs -I{} splashify tag delete {}

Common patterns

bash
# Count tags
splashify tags | jq '.tags | length'

# Get a tag's id by exact name
splashify tags | jq -r '.tags[] | select(.name == "VIP") | .id'

# Rename the "VIP" tag to "Top Customer"
ID=$(splashify tags | jq -r '.tags[] | select(.name == "VIP") | .id')
splashify tag rename "$ID" "Top Customer"

# Tag a specific contact with an existing tag (note: contact tag, not tag)
splashify contact tag <contact_id> --tags VIP,lead

Opt-out / Opt-in keyword commands

The opt command mirrors Settings → OPT Management — the keyword lists that automatically opt a contact out of (or back into) messaging, plus the optional auto-response message for each. Available since v0.1.9.

bash
splashify opt                                       # full settings (default)
splashify opt out                                   # show just opt_out
splashify opt in                                    # show just opt_in
splashify opt out add STOP UNSUBSCRIBE QUIT         # add keywords (case-insensitive dedupe)
splashify opt out remove QUIT                       # remove keywords
splashify opt out response "You have been opted out of all marketing messages."
splashify opt out response-on                       # enable the auto-response
splashify opt out response-off                      # disable the auto-response

splashify opt in  add START SUBSCRIBE
splashify opt in  remove START
splashify opt in  response "Welcome back — you'll start receiving updates again."
splashify opt in  response-on | response-off

Why writes do read-modify-write

PUT /api/v1/app/opt-settings requires both opt_out and opt_in in the body — sending just one side would clear the other. The CLI always loads the current settings first, mutates the requested side, and submits the merged payload. Same pattern as splashify waba update.

What the response shape looks like

json
{
  "success": true,
  "opt_out": {
    "keywords": ["STOP", "UNSUBSCRIBE"],
    "response": "You have been opted out.",
    "response_enabled": true
  },
  "opt_in": {
    "keywords": ["START", "SUBSCRIBE"],
    "response": "Welcome back.",
    "response_enabled": false
  }
}

jq snippets

bash
# Just the opt-out keywords
splashify opt | jq -r '.opt_out.keywords[]'

# Quick check: is auto-response enabled on either side?
splashify opt | jq '{out: .opt_out.response_enabled, in: .opt_in.response_enabled}'

# Migrate keywords from one side to the other (out → in)
splashify opt | jq -r '.opt_out.keywords[]' | \
  xargs -r splashify opt in add

Media library commands

The media command surfaces the /media page — list every file uploaded to your account, see storage usage, upload new files from disk, and delete files by media_id. Available since v0.1.8.

bash
splashify media                                 # list every file (URLs + details)
splashify media list                            # same as above (alias)
splashify media list --type image               # filter by type
splashify media storage                         # storage quota + usage
splashify media upload ./logo.png               # upload a file from disk
splashify media upload /var/files/invoice.pdf
splashify media delete 7a32bce6-910d-4b1f-...   # remove a file by media_id

What each file row contains

json
{
  "media_id":       "uuid",
  "file_name":      "media/.../1700000000-original.png",
  "original_name":  "logo.png",
  "file_url":       "https://splashify.blr1.cdn.digitaloceanspaces.com/media/...",
  "file_type":      "image",
  "mime_type":      "image/png",
  "file_size":      12345,
  "file_extension": ".png",
  "created_at":     "2026-05-19T13:57:23.582Z"
}

The file_url is the public CDN URL of the file. You can hand it to splashify message media --url <file_url> to send the file in a WhatsApp message without re-uploading.

Accepted file types

Enforced by the backend's classifyFileType:

Other extensions are refused with a 400 listing the accepted set. The exact per-type size caps live in the backend and are reported in the error message when a file is too large.

Storage quota

splashify media storage returns the current usage and limit:

bash
splashify media storage | jq
# {
#   "success":       true,
#   "storage_used":  524288,
#   "storage_limit": 5368709120,
#   "files_count":   12,
#   ...
# }

When an upload would exceed the quota the backend refuses with HTTP 400 and the JSON includes storage_used and storage_limit so the CLI surfaces a clear "storage quota exceeded" message.

Common jq one-liners

bash
# Just the URLs of every image
splashify media list --type image | jq -r '.files[].file_url'

# Total bytes used by uploaded videos
splashify media list --type video | jq '[.files[].file_size] | add'

# Most recent upload of any type
splashify media | jq '.files | sort_by(.created_at) | reverse | .[0]'

# Bulk delete every audio file (careful — irreversible)
splashify media list --type audio | jq -r '.files[].media_id' | \
  xargs -I{} splashify media delete {}

Subscription commands (read-only)

The subscription command mirrors Settings → Subscriptions — the active plan, add-ons, and the list of plans available for upgrade. Available since v0.1.6.

Read-only by design. No upgrade, no payment, no coupon validation. To change a plan, use the web app.

bash
splashify subscription                      # consolidated view
splashify subscription status               # current plan + add-ons
splashify subscription plans                # available plans you can upgrade to
splashify subscription addons               # add-ons on the current plan

Subscription gate at connect

splashify connect calls /api/v1/app/developer/cli-eligibility after the token is validated. The endpoint now refuses with reason: "subscription_expired" when:

  • The user has no active paid plan (plan_status != "active" or the plan has expired), AND
  • The trial has ended (trial_ends_at is in the past or missing).

In that case the CLI prints:

text
your trial has ended and there is no active paid plan on this account.

  The splashify CLI requires an active subscription (paid plan OR
  unexpired trial).

  Upgrade your plan:  https://app.splashifypro.com/settings/subscriptions

  Run "splashify subscription" once a plan is active to confirm,
  then re-run "splashify connect".

Free-trial users continue to work normally — the gate only fires when both trial and paid plan are unavailable.

Per-feature upgrade prompts

If a backend endpoint refuses a CLI command because the user's plan is below the tier required for that feature, the response should be:

json
{
  "success": false,
  "error": "plan_required",
  "required_plan": "GROWTH",
  "current_plan": "STARTER",
  "feature": "broadcasts.create",
  "upgrade_url": "https://app.splashifypro.com/settings/subscriptions"
}

The CLI auto-detects this shape on any HTTP 402/403 and prints:

text
error: this feature requires a higher plan (you are on STARTER, needed: GROWTH)
  Feature: broadcasts.create
  Upgrade your plan:  https://app.splashifypro.com/settings/subscriptions

The CLI never invents tier rules — the backend stays the source of truth. Adding a new tier gate only requires the backend to return this shape; the CLI displays it correctly without a new release.

Billing commands (read-only)

The billing command mirrors everything the Settings → Billing page reads — the GST billing profile, the active subscription, recent invoices, and billing logs. Available since v0.1.5.

Read-only by design. No profile update, no GSTIN validation, no certificate upload, no payment initiation. To change billing details, use the web app.

bash
splashify billing                            # consolidated view (all sections)
splashify billing profile                    # GST profile + billing address
splashify billing invoices                   # invoice list
splashify billing logs                       # billing log entries (all-time)
splashify billing logs --period 30d          # narrow the window
splashify billing logs --limit 100           # cap the result count

The default consolidated view returns:

json
{
  "profile":      { "billing": { "display_name": "...", "gst_no": "...", "billing_address": { ... } } },
  "subscription": { "user": { "plan_name": "...", "plan_status": "...", "plan_expires_at": "...", ... } },
  "wallet":       { "wallet_amount": 1234.56, ... },
  "invoices":     { "invoices": [ ... ] },
  "logs":         { ... }
}

A section that fails for any reason is reported as {"error": "..."} for that key — the rest of the view still renders.

Common one-liners with jq:

bash
# Just the billing-critical fields
splashify billing | jq '{
  legal_name:    .profile.billing.display_name,
  gst_no:        .profile.billing.gst_no,
  gst_treatment: .profile.billing.gst_treatment,
  city:          .profile.billing.billing_address.city,
  state:         .profile.billing.billing_address.state,
  plan:          .subscription.user.plan_name,
  expires:       .subscription.user.plan_expires_at,
  wallet:        .wallet.wallet_amount,
  invoice_count: (.invoices.invoices | length)
}'

# Most recent invoice
splashify billing invoices | jq '.invoices[0]'

# All invoice numbers and totals
splashify billing invoices | jq -r '.invoices[] | "\(.invoice_number)\t\(.total)\t\(.status)"'

# Billing logs for the last 30 days
splashify billing logs --period 30d | jq

Downloading an invoice PDF is not exposed as a friendly command — the backend returns a binary response. Use the URL pattern https://api.splashifypro.com/api/v1/app/invoices/<invoice_id>/download with your oc_live_ token in the Authorization header (curl works), or open the invoice from the web app.

WhatsApp Business Account (WABA) commands

The waba command mirrors everything the Dashboard page in the app shows about your connected WhatsApp Business Account — and lets you update the business profile, refresh data from Meta, register the phone, and manage the Official Business Account (OBA) status. Available since v0.1.3.

bash
splashify waba                                # show the full WABA details
splashify waba | jq '{phone_number, waba_id, quality_rating, messaging_limit_tier}'
splashify waba setup-status                   # high-level setup checklist
splashify waba sync                           # pull fresh data from Meta
splashify waba register-phone                 # (re-)register the phone with Meta
splashify waba oba-status                     # Official Business Account status
splashify waba oba-apply                      # apply for Official Business Account
splashify waba request-deletion               # request WABA deletion (destructive)

Update the business profile

bash
# Single field — other fields are preserved
splashify waba update --about "Premium WhatsApp messaging for businesses"

# Multiple fields at once
splashify waba update \
  --about "Premium WhatsApp messaging" \
  --description "Splashify Pro — broadcasts, templates, AI-driven flows" \
  --email "[email protected]" \
  --address "Kolkata, West Bengal, India" \
  --vertical "PROF_SERVICES" \
  --websites "https://splashifypro.com,https://app.splashifypro.com"

Partial updates are safe. Since v0.1.7 the CLI does a read-modify-write — it fetches the current profile, overlays whichever flags you passed, and submits the merged body. Fields you didn't mention stay untouched.

Earlier CLI versions sent a partial body that the backend blanked the omitted columns from. The backend was also patched in the same release so any client (CLI, MCP, your own scripts) can now send a partial body safely; if you are on a CLI older than v0.1.7 and the backend deploy is pending, pass every field you want to keep.

Fields returned by splashify waba

Backed by GET /api/v1/app/dashboard/whatsapp-status. The response includes:

If the response includes "needs_sync": true, run splashify waba sync; the next call will return the full set.

AI Agents commands

ai-agents (plural) and ai-agent (singular) mirror /ai-agents and the per-agent detail page — full agent CRUD, default-agent toggle, and knowledge-base management.

bash
splashify ai-agents                                # list
splashify ai-agent <agent_id>                      # show one
splashify ai-agent create --name "Bot" --agent-type support \
                          [--channel whatsapp|instagram] [--industry …] \
                          [--use-case …] [--role …] [--goal …] [--instructions …]
splashify ai-agent update <id> [--name] [--role] [--goal] [--instructions] \
                                [--status] [--tone] [--ai-provider] [--ai-model] \
                                [--temperature 0.7] [--processing-msg true|false] \
                                [--processing-msg-text …] [--image-recognition true|false]
splashify ai-agent set-default <id>
splashify ai-agent unset-default <id>
splashify ai-agent delete <id>
splashify ai-agent knowledge <id>                  # list knowledge files
splashify ai-agent knowledge <id> upload <file>    # .pdf / .docx / .md / .txt (max 15MB)
splashify ai-agent knowledge <id> delete <file_id>

Full guide: AI Agents — covers channel gates (WhatsApp vs Instagram), the sparse-PUT update semantics, knowledge base management, and end-to-end workflows.

Integrations commands

integrations mirrors /integrations — per-slug behaviour configs, OAuth account connections, and webhook event logs.

bash
splashify integrations                             # configs (default view)
splashify integrations config <slug>               # show one
splashify integrations config <slug> save \
  --enabled true --template <id> [--config '{…}'] [--vars '{…}'] \
  [--phone-field …] [--events '{…}']
splashify integrations config <slug> delete
splashify integrations accounts                    # OAuth connections
splashify integrations account <id> disconnect
splashify integrations token                       # mint a connect-token
splashify integrations logs [--limit N] [--slug …] # webhook event feed
splashify integrations log <log_id>                # one event

Full guide: Integrations — covers per-slug configs, the --events per-event automation map, OAuth account lifecycle, and webhook-log debugging.

IP allowlist commands (paid)

allowed-ips (aliases: ip-allowlist, ips) mirrors /settings/allowed-ips. Paid feature; trial users get a friendly feature_locked upgrade prompt.

bash
splashify allowed-ips                              # list
splashify allowed-ips add --name "Office" --mode single --ip 203.0.113.4
splashify allowed-ips add --name "VPN" --mode range \
                         --start 10.0.0.0 --end 10.0.0.255
splashify allowed-ips delete <entry_id>

Full guide: IP allowlist — covers behaviour when zero vs one+ entries are present, the safety-net rule that lets you always reach the allowlist endpoints, and limits (50 entries, IPv4 only).

Click-to-WhatsApp Ads commands

ctwa (alias: meta-ads) mirrors /settings/ctwa — Meta OAuth code exchange, Conversion API (CAPI) dataset + event triggers, test-event firing, and ads inventory.

bash
splashify ctwa                                     # CAPI status (default)
splashify ctwa capi                                # show CAPI config
splashify ctwa capi save [--dataset-id] [--lead-enabled] [--lead-trigger] \
                         [--lead-tag] [--purchase-enabled] [--purchase-trigger] \
                         [--purchase-tag] [--purchase-currency] [--purchase-value]
splashify ctwa capi send-event --event-type lead|purchase --phone +91… \
                                [--value 99.99] [--currency INR]
splashify ctwa exchange-code --code <code> [--granted-scopes '[…]'] [--redirect-uri …]
splashify ctwa refresh-token
splashify ctwa ads                                 # list ads (resolves user_id from /app/me)
splashify ctwa ad <ad_id>

Full guide: Click-to-WhatsApp Ads — covers the graphical OAuth handshake, the CAPI lead/purchase event triggers, the auto-create-dataset behaviour, and the per-ad inventory commands.

Template commands (WhatsApp + RCS)

The templates / template commands mirror /templates and /templates/create for WhatsApp; the rcs templates / rcs template commands mirror /templates/rcs/create for RCS.

bash
# WhatsApp
splashify templates                                      # list
splashify template <template_id>                         # show one
splashify templates sync                                 # sync ALL from Meta
splashify templates sync <template_id>                   # sync one
splashify templates upload-media <file>                  # get a Meta media handle
splashify templates create --name "promo" --language en --category MARKETING \
                            --text "Welcome to our store!"
splashify templates create --file ./template.json        # full payload from disk
splashify templates delete <template_id> [--name <name>]

# RCS
splashify rcs templates                                  # list
splashify rcs template <template_id>                     # show one
splashify rcs template <template_id> check-status        # poll approval
splashify rcs templates upload-media <file> [--height SHORT|MEDIUM|TALL]
splashify rcs templates create --name "rcs_promo" --type basic --text "Hi!"
splashify rcs templates create --name "rcs_card" --type rich_card --data '{…}'
splashify rcs templates create --name "rcs_carousel" --type carousel --file ./c.json
splashify rcs templates delete <template_id>

Full guide: see the Templates page for component grammar (HEADER/BODY/FOOTER/BUTTONS/CAROUSEL/LIMITED_TIME_OFFER), media handle two-step flow, RCS RML template_data shapes (basic / rich_card / carousel) with all five suggestion types (message, url, dial, calendar, location), bulk-sync cron, submit-and-wait scripts, and troubleshooting.

WhatsApp Flows commands

The flows and flow commands mirror the /flows page — list your Meta-managed WhatsApp Flow forms, sync them from Meta, view per-flow submissions, and deprecate old flows. The "Sync from Meta" and "Create in Meta" buttons on the page are exposed as CLI subcommands.

bash
splashify flows                                  # list flows
splashify flows sync                             # pull fresh data from Meta
splashify flows create-url                       # URL to open Meta Flow Builder

splashify flow <flow_id>                         # show one flow
splashify flow <flow_id> responses --limit 50    # list submissions
splashify flow <flow_id> deprecate               # retire a flow
splashify flow response <response_id>            # show one submission by id

Flow creation is graphical-only. Meta only exposes the Flow JSON editor through its in-browser Flow Builder. flows create-url returns the deep-link URL; open it in a browser, publish there, then flows sync to pull the new flow into your account.

Full guide: see the WhatsApp Flows page for the data lifecycle, the response shape, advanced workflows (nightly cron sync, paginated dump to CSV, deprecate-by-age cleanup), and troubleshooting.

Devices / sessions commands

The devices command (aliases: sessions, ses) and session (alias: device) mirror the Settings → Devices page — list every device signed into your Splashify Pro account and remotely log any of them out.

bash
splashify devices                              # list every active session
splashify devices list --platform web          # filter by platform
splashify devices list --ip 203.0.113.4        # filter by IP
splashify session <session_id>                 # show one session

splashify devices logout <session_id>          # revoke one session
splashify devices logout-all                   # revoke every session (prompts)
splashify devices logout-all --yes             # skip the prompt (scripts)
splashify devices logout-all --platform web    # only web sessions

The CLI keeps working after logout-all — access tokens (oc_live_…) are stored in a separate table from login sessions.

Full guide: see the Devices & Sessions page for the response shape, the is_current nuance, security notes, and end-to-end workflows (revoke by age, by IP, by platform; keep just the latest session; etc.).

Instagram automation commands

The instagram command (alias: ig) mirrors the /instagram-automation page — connect your Instagram Login account, manage comment-to-DM automation rules, send DMs in existing conversations, check Meta's reply window, sync historical chats, and read the activity log.

bash
# Setup
splashify instagram oauth-url             # → open authorize_url in a browser
splashify instagram connect --code <code>
splashify instagram                       # show connected account
splashify instagram disconnect

# Discovery
splashify instagram media --limit 25      # your IG posts (rule targets)
splashify instagram logs --limit 50       # automation activity feed
splashify instagram sync                  # backfill conversations from Meta

# Comment-to-DM rules (CRUD + toggle)
splashify instagram rules                                 # list
splashify instagram rule <id>                             # show one
splashify instagram rule create --media-id <id> \
  --keyword "buy" --dm-message "Here's the link…" \
  --comment-reply "DM sent 💌" --active true
splashify instagram rule update <id> --keyword "new"      # RMW + PATCH
splashify instagram rule toggle <id>                      # flip is_active
splashify instagram rule delete <id>

# Send a DM (gated by Meta's 24h/7d window)
splashify instagram window --conversation <conversation_id>
splashify instagram dm --conversation <id> --message "Hey!" \
  [--media-url https://… --media-type image|video|audio]

Full guide: see the Instagram Automation page for the full OAuth flow, every endpoint, the reply-window rules, the rule shape + field reference, advanced workflows (CSV-driven bulk-create, mass-toggle by media, body swaps), and troubleshooting.

Canned message commands

The canned command (aliases: cm, cms, canned-messages) mirrors the Settings → Canned Messages page — full CRUD plus the activate/deactivate toggle on saved WhatsApp replies. Supports text, image, video, audio, document, and any advanced Meta interactive payload.

bash
splashify canned                              # list every canned message
splashify canned list --type IMAGE            # filter by message_type
splashify canned <message_id>                 # show one

splashify canned create --name "Welcome" --type TEXT --text "Hi there!"
splashify canned create --name "Receipt"  --type IMAGE \
  --url https://cdn.example.com/receipt.png --caption "Your receipt"

splashify canned update <id> --text "Updated body"
splashify canned send <id|/shortcut> --to +91…   # send it to a contact
splashify canned send /menu --to +91… --dry-run  # preview the exact text
splashify canned toggle <id>                  # flip is_active
splashify canned delete <id>                  # permanent removal

Full guide: see the Canned Messages page for every message type, the Meta-Cloud-API payload shapes, advanced --payload examples (interactive buttons / list / CTA / location / contact / address), and bulk workflows.

Team / Agent commands

The team (aliases: agents, members) and member (alias: agent) commands mirror the Settings → Agents page — invite a new team member, update their role + per-page permissions, resend OTPs/invites, and remove them. The CLI wraps the two-step backend flow (POST /app/team followed by POST /app/team/verify-otps) into a single team add command that prompts for the OTP after creating the member.

bash
splashify team                                    # list members + limit + count
splashify member <id>                             # show one member

# Invite an agent with full access — prompts for the OTP that lands on
# the OWNER's email + WhatsApp
splashify team add --name "Alice" --email [email protected] \
  --country-code +91 --phone 9876543210 \
  --role agent --all read_write

splashify team verify <member_id> --otp 123456    # if you used --no-verify earlier
splashify team resend-otp <member_id>             # invalidate + re-send the create OTP
splashify team resend-invite <member_id>          # re-send the "Set Password" email
splashify team update <member_id> --role manager  # role and/or permission update
splashify team set-role <member_id> manager       # convenience — role only
splashify team set-permissions <member_id> --all read_write   # convenience — perms only
splashify team delete <member_id>                 # permanent removal

Full guide: see the Team & Agents page for the complete command reference, the page-key list, the four ways to build a permissions map, the member-limit gate, and end-to-end workflows (CSV-driven invites, bulk pending-cleanup, etc.).

Broadcast commands

bash
splashify broadcasts                          # list campaigns
splashify broadcast <id>                       # campaign detail
splashify broadcast stats                       # overall stats
splashify broadcast create --name "May Sale" --template may_offer \
  --audience-type segment --audience-id <segment-id> \
  --schedule 2026-06-01T10:00:00Z

--audience-type is segment, tag, or all. Omit --schedule to send now.

Templates, analytics & wallet

bash
splashify templates              # list approved WhatsApp templates
splashify analytics              # message analytics summary
splashify analytics trends       # analytics trends
splashify wallet                 # wallet balance
splashify wallet transactions    # wallet transaction history

Full guides: Analytics, Wallet.

Dashboard

The dashboard command mirrors the app's home /dashboard page — WABA + phone status, setup checklist, plan, wallet, Advertisement Credits, and KYC, all in one consolidated read.

bash
splashify dashboard                  # consolidated snapshot (default)
splashify dashboard setup-status     # just the setup checklist
splashify dashboard whatsapp-status  # just the WhatsApp / Meta status
splashify dashboard kyc              # KYC verification status

Full guide: Dashboard.

Profile

profile mirrors /profile — name, password, WhatsApp number (with OTP), avatar upload, and 2FA toggle. The CLI's own bearer token is unaffected by any of these — see Access Tokens.

bash
splashify profile                                     # show
splashify profile update --first-name "Alice" --last-name "Smith"
splashify profile change-password --current <old> --new <new>
splashify profile whatsapp send-otp --country-code +91 --mobile 9876543210
splashify profile whatsapp verify   --country-code +91 --mobile 9876543210 --otp 123456
splashify profile picture ./avatar.png
splashify profile 2fa enable | disable

Full guide: Profile.

Company details

bash
splashify company                                # show current company profile
splashify company update --company-name "Acme Pvt Ltd" \
                         --industry "E-commerce" \
                         --company-size 51-200 \
                         --website  https://acme.com \
                         --country  IN \
                         --state    KA \
                         --pincode  560001 \
                         --timezone Asia/Calcutta

Full guide: Company details.

Business username

bash
splashify username                    # current username + status
splashify username suggestions        # backend-suggested ideas
splashify username adopt mybrand      # claim a username
splashify username delete             # release the current username

Full guide: Business username.

Maya (AI assistant)

bash
splashify maya chat "How do I create a broadcast?"
splashify maya chat --thread-id <id> "follow-up question"
splashify maya feedback --reply-id <id> --rating up|down [--comment "…"]

Full guide: Maya.

The generic api command — everything else

Any Splashify Pro app endpoint is reachable directly, so the CLI covers the entire app surface (segments, AI agents, team, tickets, WhatsApp setup, and more):

bash
splashify api GET  /app/segments
splashify api GET  "/app/contacts?page=2&page_size=50"
splashify api POST /app/messages/send-text --data '{"phone":"+919876543210","message":"hi"}'
splashify api POST /app/ai-agents --data '{"name":"Support Bot"}'
splashify api DELETE /app/contacts/<id>
  • The path may be given with or without the /api/v1 prefix.
  • Use --data to pass a JSON request body for POST / PUT / PATCH.
  • Query parameters can be added directly in the path with ?key=value.

This guarantees full coverage — even endpoints without a dedicated friendly command can be called.

Output

Every command prints the backend's JSON response, indented for readability. On failure, the CLI prints error: … and exits with a non-zero status, which makes it easy to use in scripts.

Next