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
| Command | Description |
|---|---|
splashify connect | Connect this machine — prompts for the backend URL and an oc_live_ token |
splashify whoami | Show the connected account |
splashify doctor | Diagnose config, token, OpenClaw, and the Splashify skill |
splashify version | Print the CLI version |
splashify help | Show the full command list |
Configuration is stored at ~/.splashify/config.json (mode 0600).
Access token commands
| Command | Description |
|---|---|
splashify token list | List your access tokens |
splashify token create --name "<name>" [--expires-days N] | Create a new token |
splashify token revoke <id> | Revoke a token immediately |
See Access Tokens for details.
OpenClaw skill command
| Command | Description |
|---|---|
splashify link openclaw [--print] [--path <dir>] | Install the Splashify skill into OpenClaw’s workspace skills directory |
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
# 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| Command | Description |
|---|---|
message send --to --text | Send a text message |
message template --to --name [--lang] [--vars] | Send a template message |
message media --to --type --url [--caption] | Send image/document/video/audio |
conversations [--page] [--page-size] [--status] | List conversations |
conversation <id> [resolve] | Read or resolve a conversation |
unread | Unread message count |
Contact commands
splashify contacts --search john --tag vip
splashify contact <id>
splashify contact create --phone +919876543210 --name "John Roe" --email john@x.com
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.
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| Subcommand | Backed by |
|---|---|
| (none — default) | Merges all six endpoints below into one JSON object |
info / me / profile | GET /api/v1/app/me |
orgs / organizations | GET /api/v1/app/organizations |
invitations / invites | GET /api/v1/app/org/invitations |
sent-invitations / sent-invites | GET /api/v1/app/org/sent-invitations |
wallet | GET /api/v1/app/wallet/info |
The default consolidated view returns:
{
"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:
# 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.
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| Subcommand | Endpoint |
|---|---|
support / support list [--status --search] / tickets | GET /api/v1/app/tickets (filters are client-side, same as the page) |
ticket <id> | GET /api/v1/app/tickets/:ticket_id |
ticket create --title --description --category [--priority] | POST /api/v1/app/tickets |
ticket <id> reply "<message>" | POST /api/v1/app/tickets/:ticket_id/replies |
ticket <id> close | POST /api/v1/app/tickets/:ticket_id/close |
Categories and priorities (match the page’s dialogs)
--category | What it means |
|---|---|
bug | Something isn’t working |
billing | Payments & subscriptions |
feature_request | Suggest an improvement |
account | Login, profile & settings |
general | Anything else |
--priority | When to use |
|---|---|
low | Cosmetic / non-blocking |
medium | Default; affects a workflow but has a workaround |
high | Affects a workflow, no workaround |
urgent | Production outage / billing-blocker |
Statuses you’ll see (read-only)
| Status | Meaning |
|---|---|
open | New, not yet picked up |
in_progress | Support team is working on it |
waiting_for_user | Support replied; reply needed from you |
resolved | Marked resolved by support |
closed | Closed (by you or after resolution) |
Reply syntax shortcut
reply joins everything after it with spaces, so you don’t have to quote a
short message:
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
# 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 {} closeTrack 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.
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| Subcommand | Endpoint / behaviour |
|---|---|
expenses / expenses summary [--period] | GET /api/v1/app/expenses/summary?period=… |
expenses categories [--period] | Same endpoint, JSON trimmed to category fields |
expenses countries [--period] | Same endpoint, JSON trimmed to top_countries |
expenses trends [--period] | GET /api/v1/app/expenses/trends?period=… |
expenses logs [--period --limit --category --country --free-trial] | GET /api/v1/app/expenses/billing-logs?period=…&limit=… (filters client-side) |
expenses export [--period --limit --out] | Same endpoint, formatted as the page’s CSV |
--period values (same five the page exposes)
| Value | Range |
|---|---|
7d | Last 7 days |
30d (default) | Last 30 days |
3m | Last 3 months |
6m | Last 6 months |
all | Every deduction on record |
What summary returns
| Field | Meaning |
|---|---|
total_deducted | Net spend in the period (broadcasts settled net of refunds) |
total_messages | Net paid message count |
marketing_amount / marketing_count | Per-bucket roll-up (also utility_*, auth_*, rcs_*, call_*) |
avg_per_message | total_deducted / total_messages |
top_countries | Top 5 destinations by amount ({code, count, amount}) |
sent_count / delivered_count | Trigger-level message counts |
free_trial_count / paid_count | Free-trial vs. billed split |
currency | Always INR today |
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.
| Flag | Filter |
|---|---|
--category | marketing, utility, authentication, rcs, call, broadcast_deduction, broadcast_refund |
--country | ISO country code, e.g. IN, US, BR |
--free-trial | true (free-trial deductions only) or false (billed only) |
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:
Date, Recipient, Category, Country, Total Deducted, Trigger, Free TrialDefault output is stdout; pass --out file.csv to write to disk.
Common patterns
# 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:
GET /app/developer/cli-eligibility→ refuses with the documentedsubscription_expired/no_waba/account_suspendedmessages.GET /app/wallet/info→ prints balance; refuses whenwallet_amount ≤ 0on 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
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"| Subcommand | Endpoint |
|---|---|
calling / calling overview | GET /api/v1/app/calling |
calling settings | GET /api/v1/app/calling/settings |
calling analytics […] | GET /api/v1/app/calling/analytics |
calling calls […] | GET /api/v1/app/calling/calls |
call <call_id> | GET /api/v1/app/calling/calls/:call_id |
calling permission-status --phone … | GET /api/v1/app/calling/permission-status |
calling permissions […] | GET /api/v1/app/calling/permissions |
calling templates | GET /api/v1/app/calling/templates |
calling template status --name … | GET /api/v1/app/calling/templates/status |
Write — initiate, templates, sends, settings update, recording upload
# 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": []
}
}'| Subcommand | Endpoint |
|---|---|
call initiate --to … | POST /api/v1/app/calling/initiate |
call upload-recording <id> <file> | POST /api/v1/app/calling/calls/:call_id/recording (multipart, field recording) |
calling template create-call-button … | POST /api/v1/app/calling/templates/call-button |
calling template create-permission … | POST /api/v1/app/calling/templates/permission |
calling template set-default <id> | POST /api/v1/app/calling/templates/set-default |
calling send call-button … | POST /api/v1/app/calling/send-call-button |
calling send permission … | POST /api/v1/app/calling/send-permission |
calling send template … | POST /api/v1/app/calling/send-template |
calling send permission-template … | POST /api/v1/app/calling/send-permission-template |
calling settings update --data '{…}' | PUT /api/v1/app/calling/settings (body wrapped as {"calling": …}) |
What preflight failure looks like
$ 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:
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
# 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 5Common patterns
# 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.jsonBroadcasts
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:
- Subscription —
GET /api/v1/app/developer/cli-eligibility. Refuses with the existingsubscription_expired/no_waba/account_suspendedmessages (same assplashify connect). - Wallet balance —
GET /api/v1/app/wallet/info. Prints the current balance; if it’s≤ 0the 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:
--yesoncreate/rebroadcastskips the balance soft-check.SPLASHIFY_SKIP_PREFLIGHT=1skips both checks.- The backend still enforces per-message via
wallet_billing_service.CheckBalance— preflight is for fast, friendly UX, not security.
Read
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)| Subcommand | Endpoint |
|---|---|
broadcasts […] | GET /api/v1/app/broadcasts |
broadcast stats [--period] | GET /api/v1/app/broadcasts/stats |
broadcast <id> [--recompute] | GET /api/v1/app/broadcasts/:id |
broadcast <id> messages […] | GET /api/v1/app/broadcasts/:id/messages |
broadcast <id> cohorts | GET /api/v1/app/broadcasts/:id/cohort-counts |
broadcast <id> export […] | GET /api/v1/app/broadcasts/:id/export |
Write
# 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| Subcommand | Endpoint |
|---|---|
broadcast create … | POST /api/v1/app/broadcasts |
broadcast <id> cancel | POST /api/v1/app/broadcasts/:id/cancel |
broadcast <id> restart | POST /api/v1/app/broadcasts/:id/restart |
broadcast <id> send-now | POST /api/v1/app/broadcasts/:id/send-now |
broadcast <id> rebroadcast --cohort … | POST /api/v1/app/broadcasts/:id/rebroadcast |
Required fields for create
| Flag | Required | Notes |
|---|---|---|
--name | yes | Campaign name |
--template | yes | Approved template name (lower-cased before send) |
--category | yes | MARKETING, UTILITY, AUTHENTICATION |
--language | yes | Language code (defaults to en) |
--segment-ids or --contact-ids | yes | At least one |
--send-type | no | now (default) or scheduled |
--scheduled-at | conditional | Required when --send-type=scheduled |
--params | conditional | Required if the template has variables/buttons (full Meta payload) |
--media-url | conditional | Required for header-media templates |
--rate-limit / --batch-size | no | Throttle per second / batch size |
--rcs-fallback-* | no | WA→RCS fallback (account must have rcs_enabled) |
--rcs | no | Pure-RCS broadcast |
--yes | no | Skip the balance soft-check |
What happens when preflight fails
$ 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:
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:
error: HTTP 402: Insufficient balance. Required: ₹0.95, Available: ₹0.20End-to-end recipe
# 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 failedCommon patterns
# 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
splashify email # dashboard stats (default)
splashify email stats # sameBacked 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.
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| Subcommand | Endpoint |
|---|---|
domains | GET /api/v1/app/email/domains |
domain <domain> | GET /api/v1/app/email/domains/:domain |
domain add <domain> | POST /api/v1/app/email/domains |
domain verify <domain> | POST /api/v1/app/email/domains/:domain/verify |
domain delete <domain> | DELETE /api/v1/app/email/domains/:domain |
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.
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>| Subcommand | Endpoint |
|---|---|
templates | GET /api/v1/app/email/templates |
template <id> | GET /api/v1/app/email/templates/:id |
template create --name --subject --file/--data | POST /api/v1/app/email/templates |
template update <id> [--name --subject --file/--data] | PUT /api/v1/app/email/templates/:id (read-modify-write) |
template delete <id> | DELETE /api/v1/app/email/templates/:id |
template preview --file/--data [--vars '{…}'] | POST /api/v1/app/email/templates/preview |
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
splashify email audience # stats (default)
splashify email audience stats # sameGET /api/v1/app/email/audience/stats.
Contacts
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 "a@x.com,b@x.com,c@x.com" \
--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>| Subcommand | Endpoint |
|---|---|
audience contacts [--status --search] | GET /api/v1/app/email/audience/contacts |
audience contact <id> | GET /api/v1/app/email/audience/contacts/:id |
audience contact add --emails --metadata --segments | POST /api/v1/app/email/audience/contacts |
audience contact update <id> [--status --metadata] | PUT /api/v1/app/email/audience/contacts/:id |
audience contact delete <id> | DELETE /api/v1/app/email/audience/contacts/:id |
Segments
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>| Subcommand | Endpoint |
|---|---|
audience segments | GET /api/v1/app/email/audience/segments |
audience segment <id> | List-and-filter (no per-id GET) |
audience segment create --name [--description] | POST /api/v1/app/email/audience/segments |
audience segment update <id> [--name --description] | PUT /api/v1/app/email/audience/segments/:id |
audience segment delete <id> | DELETE /api/v1/app/email/audience/segments/:id |
audience segment <id> add-contacts <ids> | POST /api/v1/app/email/audience/segments/:id/contacts |
audience segment <id> remove-contacts <ids> | DELETE /api/v1/app/email/audience/segments/:id/contacts |
Campaigns (/email/campaigns)
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 "hi@yourdomain.com" \
--reply-to "support@yourdomain.com" \
--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 "hi@yourdomain.com" \
--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| Subcommand | Endpoint |
|---|---|
campaigns | GET /api/v1/app/email/campaigns |
campaign <id> | GET /api/v1/app/email/campaigns/:id |
campaign create --name --template-id --from-name --from-email … | POST /api/v1/app/email/campaigns |
campaign send <id> | POST /api/v1/app/email/campaigns/:id/send |
campaign cancel <id> | POST /api/v1/app/email/campaigns/:id/cancel |
--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
# 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 "a@x.com,b@x.com" --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 "hi@example.com" \
--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
# 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}'AI / Voice AI credits (read-only)
The credits command mirrors the credit widgets on /dashboard — your
general AI credit 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.
splashify credits # consolidated view (all sections)
splashify credits ai # AI credit balance + info
splashify credits transactions # AI credit transaction history
splashify credits voice # voice AI rate + balance + trial
splashify credits agents # voice AI agents
# Aliases
splashify credit # singular
splashify ai-credits # full word| Subcommand | Backed by |
|---|---|
| (none — default) | Merges all three reads below into one JSON object |
ai / info | GET /api/v1/app/ai-credits/info |
transactions / tx | GET /api/v1/app/ai-credits/transactions |
voice / voice-ai | GET /api/v1/app/voice-ai/rate |
agents | GET /api/v1/app/voice-ai/agents |
Voice AI rate response
{
"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
{
"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
# 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 AI credit 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.
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| Flag | Sent to backend | Notes |
|---|---|---|
--action <name> | action=<name> | login, logout, created, updated, deleted, assigned, unassigned, blocked, unblocked, resolved, reopened |
--entity <type> | entity_type=<type> | auth, contact, conversation, template, message, tag |
--entity-id <id> | entity_id=<id> | Narrow to one specific entity |
--actor <user_id> | actor_id=<user_id> | Narrow to one specific user |
--limit <N> | limit=<N> | Server default if omitted (page uses 100) |
--search "<q>" | (none — client-side) | Substring match across actor_name, actor_email, description, entity_name. Mirrors the page’s search box. |
Output shape
{
"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": "john@acme.com",
"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
# 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
splashify attributes # list every attribute
splashify attributes --search co # client-side substring filter on label
splashify attribute <id> # show one (list-and-filter)Write
# 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
| Subcommand | Endpoint |
|---|---|
attributes [--search …] | GET /api/v1/app/attributes (filter client-side) |
attribute <id> | List-and-filter (backend has no per-id GET) |
attribute create … | POST /api/v1/app/attributes |
attribute update <id> … (alias edit) | PUT /api/v1/app/attributes/:id |
attribute delete <id> (aliases rm, remove) | DELETE /api/v1/app/attributes/:id |
attribute toggle-visibility <id> (alias toggle) | POST /api/v1/app/attributes/:id/toggle-visibility |
attribute reorder <id> up|down (alias move) | POST /api/v1/app/attributes/reorder |
Attribute types
| Type | Accepts on the contact | Notes |
|---|---|---|
TEXT | any string | Default; labels with spaces become underscores |
NUMBER | numeric | |
EMAIL | RFC-5322 email | |
PHONE | E.164 phone | |
DATE | ISO 8601 date | |
SELECT | one of --options | --options "a,b,c" required |
MULTISELECT | subset of --options | --options "a,b,c" required |
URL | URL | |
CHECKBOX | bool |
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
# 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
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)| Subcommand | Endpoint |
|---|---|
segments [--search --page --limit] | GET /api/v1/app/segments |
segments stats | GET /api/v1/app/segments/stats |
segment <id> | GET /api/v1/app/segments/:id |
segment <id> contacts [--page --limit] | GET /api/v1/app/segments/:id/contacts |
segment <id> count | GET /api/v1/app/segments/:id/count |
segment <id> refresh / recompute | POST /api/v1/app/segments/:id/refresh |
Write
# 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>| Subcommand | Endpoint |
|---|---|
segment create --name --filters … | POST /api/v1/app/segments |
segment update <id> [flags…] (aliases: edit) | PATCH /api/v1/app/segments/:id |
segment delete <id> (aliases: rm, remove) | DELETE /api/v1/app/segments/:id |
Filter DSL reference
The --filters JSON is shipped to the backend verbatim. Shape:
{
"conditions": [
{"field": "<field>", "operator": "<operator>", "value": "<value>"},
{"field": "<field>", "operator": "<operator>"} // value omitted for isEmpty / isNotEmpty
],
"logic": "and" // or "or"
}Fields (from CreateSegmentDialog):
| Field | Type | Notes |
|---|---|---|
displayName | text | Contact’s display name |
phoneNumber | text | E.164 phone, e.g. +91… |
email | text | |
tags | array of strings | Use includes / notIncludes |
hasOptedOut | bool | Marketing opt-in flag |
isBlocked | bool | |
createdAt | timestamp | ISO 8601 |
updatedAt | timestamp | ISO 8601 |
Operators:
| Operator | Works on |
|---|---|
equals, notEquals | text, bool |
contains, notContains | text |
startsWith, endsWith | text |
isEmpty, isNotEmpty | text (value omitted) |
includes, notIncludes | array (tags) |
The web app (
/settings/segments) is a visual builder for this DSL. Use it once to design a complex segment, thensplashify segment <id>to see the JSON it produced, then build similar segments programmatically.
Common jq patterns
# 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 {} refreshTag CRUD commands
The tag / tags commands mirror Settings → Tags — full CRUD on the
tag library used to organise contacts. Available since v0.1.10.
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)| Command | Endpoint |
|---|---|
tags [--search ...] | GET /api/v1/app/tags (filter is client-side) |
tag create "<name>" | POST /api/v1/app/tags {"name":"…"} |
tag rename <id> "<name>" / tag update … / tag edit … | PUT /api/v1/app/tags/:id {"name":"…"} |
tag delete <id> / tag rm <id> | DELETE /api/v1/app/tags/:id |
Output shape
{
"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:
# 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
# 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,leadOpt-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.
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| Subcommand | What it does |
|---|---|
(none — default) / status / show | GET /app/opt-settings — both sides |
out / in | Print only that section |
out add <kw1> [<kw2> ...] | Append keywords to opt_out.keywords; existing entries (case-insensitive) are skipped |
out remove <kw1> [<kw2> ...] | Drop matching keywords (case-insensitive) |
out response "<text>" | Set opt_out.response ("" to clear) |
out response-on / response-off | Toggle opt_out.response_enabled |
in ... | Same actions, applied to opt_in |
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
{
"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
# 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 addMedia 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.
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| Subcommand | Backed by |
|---|---|
(none — default) / list / ls | GET /api/v1/app/media[?type=...] |
storage / quota | GET /api/v1/app/media/storage |
upload <path> | POST /api/v1/app/media/upload (multipart, field file) |
delete <media_id> / rm <media_id> | DELETE /api/v1/app/media/:media_id |
What each file row contains
{
"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:
| Type | Extensions | Max size |
|---|---|---|
image | .jpg .jpeg .png | per backend config |
video | .mp4 | per backend config |
audio | .mp3 .ogg .webm .aac .amr .m4a | per backend config |
document | .pdf .xlsx .xls .doc .docx .csv | per backend config |
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:
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
# 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.
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| Subcommand | Backed by |
|---|---|
| (none — default) | Merges three reads: /app/plans/subscription, /app/developer/cli-eligibility, /plans |
status / current | GET /api/v1/app/plans/subscription |
plans / available | GET /api/v1/plans |
addons | Projection of addons[] from /app/plans/subscription |
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_atis in the past or missing).
In that case the CLI prints:
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:
{
"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:
error: this feature requires a higher plan (you are on STARTER, needed: GROWTH)
Feature: broadcasts.create
Upgrade your plan: https://app.splashifypro.com/settings/subscriptionsThe 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.
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| Subcommand | Backed by |
|---|---|
| (none — default) | Merges all five reads below into one JSON object |
profile | GET /api/v1/app/billing |
invoices | GET /api/v1/app/invoices |
logs [--period --limit] | GET /api/v1/app/expenses/billing-logs |
The default consolidated view returns:
{
"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:
# 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 | jqDownloading 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>/downloadwith youroc_live_token in theAuthorizationheader (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.
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
# 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 "hello@splashifypro.com" \
--address "Kolkata, West Bengal, India" \
--vertical "PROF_SERVICES" \
--websites "https://splashifypro.com,https://app.splashifypro.com"Partial updates are safe. Since
v0.1.7the 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.7and the backend deploy is pending, pass every field you want to keep.
| Flag | What it sets |
|---|---|
--about | Short bio (Meta limit ~139 chars) |
--description | Longer business description |
--address | Physical address |
--email | Contact email |
--vertical | Business category. Meta enum: AUTO, BEAUTY, APPAREL, EDU, ENTERTAIN, EVENT_PLAN, FINANCE, GROCERY, GOVT, HOTEL, HEALTH, NONPROFIT, PROF_SERVICES, RETAIL, TRAVEL, RESTAURANT, NOT_A_BIZ, OTHER |
--websites | Comma-separated URLs (Meta allows up to 2) |
--data | Raw JSON merged on top of the flags — for advanced fields like profile_picture_handle |
Fields returned by splashify waba
Backed by GET /api/v1/app/dashboard/whatsapp-status. The response includes:
| Group | Fields |
|---|---|
| Identifiers | waba_id, phone_number_id, phone_number, business_id, display_name |
| Verification | verified_name, code_verification_status, name_status, business_verification_status, is_official_business_account |
| Quality & limits | quality_rating, messaging_limit_tier, throughput_level |
| State | waba_status, phone_status, platform_type, is_on_biz_app, last_synced_at |
| Business profile | profile_about, profile_description, profile_address, profile_email, profile_vertical, profile_websites, profile_picture_url |
| Account | waba_name, waba_currency |
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.
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.
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 eventFull guide: Integrations — covers per-slug configs, the
--eventsper-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.
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.
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.
# 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.
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 idFlow creation is graphical-only. Meta only exposes the Flow JSON editor through its in-browser Flow Builder.
flows create-urlreturns the deep-link URL; open it in a browser, publish there, thenflows syncto 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.
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 sessionsThe 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_currentnuance, 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.
# 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.
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 toggle <id> # flip is_active
splashify canned delete <id> # permanent removalFull guide: see the Canned Messages page for every message type, the Meta-Cloud-API payload shapes, advanced
--payloadexamples (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.
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 alice@x.com \
--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 removalFull 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
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
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 historyDashboard
The dashboard command mirrors the app’s home /dashboard page —
WABA + phone status, setup checklist, plan, wallet, AI credits, and
KYC, all in one consolidated read.
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 statusFull 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.
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 | disableFull guide: Profile.
Company details
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/CalcuttaFull guide: Company details.
Business username
splashify username # current username + status
splashify username suggestions # backend-suggested ideas
splashify username adopt mybrand # claim a username
splashify username delete # release the current usernameFull guide: Business username.
Maya (AI assistant)
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):
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/v1prefix. - Use
--datato pass a JSON request body forPOST/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
- Access Tokens
- Troubleshooting
- OpenClaw skill — run these tasks by talking to an AI assistant.