Skip to Content
splashify CLIInstagram Automation

Instagram Automation

The instagram command (alias: ig) mirrors the /instagram-automation page in the app. From the terminal you can connect your Instagram Login account, list the posts on it, build comment-to-DM automation rules, send DMs in existing conversations, sync historical chats from Meta, and read the automation activity log.

Backed by /api/v1/app/instagram/*. Every call uses your stored oc_live_ token and acts as you — only the account owner can manage Instagram on a team account.

Prerequisites. Your account must have the Instagram Automation feature enabled (the backend gates this with EnsureFeatureEnabled). If your plan doesn’t include it, the CLI surfaces the standard plan_required upgrade prompt with the exact upgrade URL.

Quick start

# 1. Get the OAuth URL and open it in your browser splashify instagram oauth-url # 2. After the IG redirect, copy the `code` query parameter from the URL splashify instagram connect --code <code> # 3. Confirm splashify instagram # show the connected account splashify instagram media --limit 25 # see what you can target with rules # 4. Build a rule splashify instagram rule create \ --media-id 17841401234567890 \ --keyword "shipping" \ --dm-message "Hey! Free shipping today only — code SHIP10 ✨" \ --comment-reply "Sent you a DM 💌" \ --active true # 5. Watch it fire splashify instagram logs --limit 20

How the connect flow works

The handshake is a three-leg dance and the CLI handles the server-side leg only — the in-browser step still needs a real browser because Instagram Login requires the user to authenticate on instagram.com:

  1. splashify instagram oauth-url returns an authorize_url you open in a browser. The URL embeds your user_id as state so the redirect attributes the connection to the right account.
  2. Instagram redirects to https://app.splashifypro.com/instagram/callback with ?code=...&state=... in the query string. Copy the code value.
  3. splashify instagram connect --code <code> exchanges the code for a long-lived (~60 day) access token server-side, subscribes our app to webhooks for comments + messages, and stamps the row in app_ig_accounts.

After step 3, splashify instagram returns the full account record. Use splashify instagram disconnect to undo (drops every rule and log too).

Command reference

splashify instagram — show the connected account

splashify instagram # default splashify instagram account # alias splashify instagram info # alias

Returns {success, account} where account is null when nothing’s connected, or:

{ "ig_user_id": "17841401234567890", "ig_username": "your.handle", "ig_name": "Your Brand", "ig_profile_pic": "https://…", "account_type": "BUSINESS", "connected_at": "2026-04-12T10:23:04Z", "webhooks_subscribed_at": "2026-04-12T10:23:07Z", "token_expires_at": "2026-06-11T10:23:04Z" }
Backed byGET /api/v1/app/instagram/account

splashify instagram oauth-url

splashify instagram oauth-url splashify instagram authorize-url # alias

Returns the URL to open in a browser plus the redirect_uri the backend expects to handle the callback (https://app.splashifypro.com/instagram/callback in production).

Backed byGET /api/v1/app/instagram/oauth-url

splashify instagram connect --code <code>

Completes the OAuth handshake on the backend.

splashify instagram connect --code AQB0Lg…JaYHFq8KQ
Backed byPOST /api/v1/app/instagram/connect {code}

Effects:

  • Exchanges the code for a short-lived token, then upgrades to a long-lived (~60 day) token.
  • Reads /me for username, profile pic, account type.
  • Persists the encrypted token in app_ig_accounts.
  • Subscribes the app to webhooks for comments + messages events on this IG user (best-effort — surfaces in webhooks_subscribed_at).

splashify instagram disconnect

splashify instagram disconnect
Backed byDELETE /api/v1/app/instagram/disconnect

Unsubscribes webhooks, deletes the account row, and removes every rule + log entry for this user. There is no undo. To reconnect later, run the whole OAuth dance again — a fresh long-lived token is minted and webhooks re-subscribed.

splashify instagram media [--limit N]

splashify instagram media # default limit: 50 splashify instagram media --limit 25
Backed byGET /api/v1/app/instagram/media?limit=N

Proxies Meta’s /me/media listing. Each item has id, caption, media_type, thumbnail_url, permalink, timestamp — exactly the fields you pass to rule create as --media-id, --media-caption, --media-thumbnail, --media-permalink.

splashify instagram logs [--limit N]

splashify instagram logs # newest 50 splashify instagram logs --limit 200
Backed byGET /api/v1/app/instagram/logs?limit=N

The activity feed — every comment-to-DM trigger gets a row here (matched keyword, sender username, rule id, status, error message if Meta refused). Use this to debug why a rule didn’t fire.

splashify instagram window --conversation <id>

splashify instagram window --conversation 1c5d2f5e-…
Backed byGET /api/v1/app/instagram/messages/window?conversation_id=…

Meta enforces a strict reply window for Instagram DMs:

  • 0–24h since the user last messaged you — free-text DMs allowed.
  • 24h–7d — only HUMAN_AGENT tagged messages (the backend sets this automatically — you don’t pass it).
  • >7d — no outbound DMs at all until the user messages you again.

The composer in /messages calls this before every send. From the CLI, use it as a pre-check so a dm won’t fail with HTTP 403.

Response shape:

{ "success": true, "window": { "can_reply": true, "needs_human_agent": false, "reply_window_ends_at": "2026-05-21T14:00:00Z", "last_inbound_at": "2026-05-20T14:00:00Z" } }

splashify instagram sync

splashify instagram sync
Backed byPOST /api/v1/app/instagram/sync-conversations

Pulls historical Instagram conversations from Meta and back-fills them into /messages. Useful right after connect so you have something to reply to. Returns a {messages_synced, conversations_synced} summary.

splashify instagram dm — send a DM

# Text only splashify instagram dm --conversation <conversation_id> --message "Hey!" # Media only splashify instagram dm --conversation <conversation_id> \ --media-url https://cdn.example.com/img.jpg --media-type image # Both — IG renders them as two bubbles splashify instagram dm --conversation <conversation_id> \ --message "Here's the file you asked for ↓" \ --media-url https://cdn.example.com/brochure.pdf --media-type image
Backed byPOST /api/v1/app/instagram/messages
FlagRequired?Notes
--conversationyesconversation_id from splashify conversations (must be an instagram channel row)
--messageone ofText body
--media-url + --media-typeone ofPublic media URL + image, video, or audio

Validation:

  • Refuses with HTTP 403 if the 7-day window has expired (splashify instagram window is the pre-check).
  • Refuses with HTTP 400 if the conversation isn’t an Instagram channel (i.e. it’s a WhatsApp conv).
  • Refuses with HTTP 502 if Meta refuses the upload (file too large, unsupported format, URL unreachable) — the CLI surfaces Meta’s exact error code (subcode 2018007 is the most common one).

Two bubbles. When you pass --message AND --media-url, IG’s /me/messages API sends them as two separate messages. The CLI inserts two rows in app_messages matching that — each with its own wa_message_id so the echo webhook can dedup correctly.

Comment-to-DM rules

The rule / rules commands manage automation rules — when a comment on a chosen IG post contains a trigger_keyword, the system sends the commenter a DM (and optionally a public reply on their comment).

Anatomy of a rule

FieldRequired?Notes
media_idyesThe IG post the rule attaches to. Get IDs from splashify instagram media
trigger_keywordyesLowercased server-side; matched case-insensitively against comment text
dm_messageyesThe text DM sent on a match
dm_media_urloptionalIf set, also sends an image / video / audio bubble
dm_media_typewith dm_media_urlimage, video, or audio (defaults to image if unset)
comment_replyoptionalPublic reply left on the matching comment
is_activeoptionalDefaults to true
media_caption / media_thumbnail / media_permalinkoptionalCached for the rules list UI — pass the values from media for a nicer display

List rules

splashify instagram rules # full list, newest first splashify instagram rules list # alias
Backed byGET /api/v1/app/instagram/rules

Response shape per rule:

{ "rule_id": "uuid", "media_id": "17841401234567890", "media_caption": "Spring sale — link in bio", "media_thumbnail": "https://…", "media_permalink": "https://www.instagram.com/p/…", "trigger_keyword": "shipping", "dm_message": "Hey! Free shipping today only…", "dm_media_url": "", "dm_media_type": "", "comment_reply": "Sent you a DM 💌", "is_active": true, "triggered_count": 12, "created_at": "2026-04-12T11:00:00Z", "updated_at": "2026-04-12T11:00:00Z" }

Show one rule

splashify instagram rule <rule_id>

The CLI fetches the list and filters client-side (no per-id GET on the backend).

Create a rule

splashify instagram rule create \ --media-id <media_id_from_media_list> \ --keyword "buy" \ --dm-message "Here's the link — https://shop.example.com/?utm=ig" \ --comment-reply "DM sent 💌" \ --active true

Optional flags:

splashify instagram rule create \ --media-id <id> --keyword "free" --dm-message "Use code FREE15 …" \ --dm-media-url https://cdn.example.com/coupon.jpg --dm-media-type image \ --media-caption "Spring drop — comment FREE for code"
Backed byPOST /api/v1/app/instagram/rules

Update a rule

splashify instagram rule update <rule_id> --keyword "new-keyword" splashify instagram rule update <rule_id> --dm-message "Updated body" --active false splashify instagram rule update <rule_id> --dm-media-url "" --dm-media-type "" # clear the media
Backed byPATCH /api/v1/app/instagram/rules/:rule_id

Read-modify-write. The backend’s PATCH writes every editable column on every call (trigger_keyword, dm_message, dm_media_url, dm_media_type, comment_reply, is_active), so a partial flag set would blank the others. The CLI loads the rule first, overlays only your explicitly-passed flags, then submits the full body — fields you didn’t mention stay intact. Same pattern as splashify canned update, waba update, opt ….

Toggle activate / deactivate

splashify instagram rule toggle <rule_id>

There is no dedicated toggle endpoint for IG rules (unlike canned messages), so the CLI implements this as load → flip is_active → PATCH. The output is the standard PATCH response.

Delete a rule

splashify instagram rule delete <rule_id> splashify instagram rule rm <rule_id> # alias
Backed byDELETE /api/v1/app/instagram/rules/:rule_id

Permanent. The row is also removed from the by-media index so the rule won’t fire on incoming comments.

End-to-end workflows

Bootstrap automation from scratch

# 1. Connect splashify instagram oauth-url # (open the URL, paste the code from the redirect) splashify instagram connect --code AQB… # 2. Discover targets splashify instagram media --limit 50 | jq '.media[] | {id, caption: (.caption // "")[:80], permalink}' # 3. Create one rule per target post (e.g. from a CSV) # csv format: media_id,keyword,dm,comment_reply while IFS=, read -r mid kw dm reply; do splashify instagram rule create \ --media-id "$mid" --keyword "$kw" --dm-message "$dm" --comment-reply "$reply" --active true done < ig-rules.csv # 4. Verify splashify instagram rules | jq '.rules[] | {rule_id, keyword: .trigger_keyword, active: .is_active}'

Deactivate every rule on a specific post

TARGET_MEDIA=17841401234567890 splashify instagram rules | \ jq -r --arg m "$TARGET_MEDIA" '.rules[] | select(.media_id == $m) | .rule_id' | \ xargs -I{} splashify instagram rule toggle {}

Find the rule that fired most often last week

splashify instagram rules | \ jq 'first(.rules | sort_by(.triggered_count) | reverse | .[]) | {rule_id, keyword: .trigger_keyword, fired: .triggered_count, dm: .dm_message[:60]}'

Replace the DM body on every active rule

NEW_DM="Updated promo — see https://shop.example.com/spring" splashify instagram rules | \ jq -r '.rules[] | select(.is_active) | .rule_id' | \ xargs -I{} splashify instagram rule update {} --dm-message "$NEW_DM"

Reply to an active conversation

# Find an open IG conversation CONV=$(splashify conversations --status open | \ jq -r '.conversations[] | select(.channel == "instagram") | .id' | head -1) # Pre-check the reply window splashify instagram window --conversation "$CONV" | jq '.window' # Send splashify instagram dm --conversation "$CONV" --message "Thanks for the message!"

Re-upload the welcome image + swap it on every rule

NEW_URL=$(splashify media upload ./welcome-v2.jpg | jq -r '.file_url') splashify instagram rules | jq -r '.rules[] | select(.dm_media_url != "") | .rule_id' | \ xargs -I{} splashify instagram rule update {} --dm-media-url "$NEW_URL" --dm-media-type image

Troubleshooting

“Instagram is not configured on the server” — the partner backend doesn’t have INSTAGRAM_APP_ID / INSTAGRAM_APP_SECRET set in its env. Reseller-only — contact your provider.

splashify instagram returns { "account": null } — you haven’t connected an IG account yet. Run the oauth-url → browser → connect flow above.

splashify instagram connect returns “Could not complete Instagram login” — the --code was already redeemed (codes are one-use), or the backend’s INSTAGRAM_REDIRECT_URI doesn’t match the one configured in the Meta app. Get a fresh code from oauth-url.

splashify instagram rule create returns “media_id, trigger_keyword and dm_message are required” — at least one of those is empty after trimming whitespace. The CLI catches this before the round-trip, but if you only see it from --data / generic api you forgot a required field.

DM send returns “This user hasn’t messaged you in over 7 days …” — Meta’s 7-day window expired. Wait for the user to message first; there’s no override.

DM send returns “Instagram couldn’t accept this attachment” — Meta subcode 2018007. The most common causes: image > 8MB, video > 25MB, unsupported format (must be JPG/PNG/MP4/MP3/WAV), or the URL is unreachable from Meta’s servers. Test the URL with curl -I first.

Rule fires but no DM arrives — check splashify instagram logs. If the log row shows status: "skipped" the commenter is your own IG account (self-comments never trigger). If it shows a Meta error code, the access token may have expired — reconnect.

Webhook subscription stuck — the response after connect shows webhooks_subscribed_at: null. The subscribe is best-effort + async; re-run splashify instagram connect --code <code> (with a fresh code) to retry, or wait — every successful send refreshes the subscription.

See also