CLI Commands
Complete reference for all Chirpie CLI commands.
Posts
Create a Post
chirpie post "Your post text here"| Flag | Description |
|---|---|
-a, --account <id> | Account ID (auto-selects if you have one account). Repeat it to publish the same post to several accounts at once |
--config <json-or-file> | Per-account overrides for a multi-account post, as JSON or a path to a JSON file. Keyed by account ID, each value taking text, media, first_comment and configuration, which is where the options a placement carries go (collaborators, user_tags, cover, video_cover_timestamp_ms, share_to_feed, trial_reel on Instagram, link on a Facebook Page). See Post to several accounts at once |
-m, --media <files...> | Image or video files on this machine, or public URLs. Files are uploaded first. Up to 4 on most platforms (JPEG, PNG, WebP, GIF up to 5MB; MP4/MOV up to 512MB) |
--alt <text...> | Describe each item for people using a screen reader, in the same order as --media |
--first-comment <text> | Post this as a comment under the post, as soon as it goes out. X, Threads, Instagram and Facebook only. It counts as one post against your monthly quota |
--instagram-placement <placement> | Where the post goes on Instagram: feed (the default), story or reel. A story takes exactly one image or video, no caption (pass "" as the text) and no first comment. A reel takes exactly one video. See Instagram publishing options |
--facebook-placement <placement> | Where the post goes on a Facebook Page: feed (the default) or story. A story takes exactly one image or video, no text and no first comment. See Facebook publishing options |
-s, --schedule <datetime> | Schedule for a future time. An ISO 8601 timestamp carrying a timezone (2026-04-01T14:00:00Z, 2026-04-01T16:00:00+02:00), or a local time with no offset (2026-11-01T09:30) read in --timezone or the timezone saved on your account |
--timezone <iana> | The IANA timezone a --schedule with no offset is read in, such as America/New_York. Daylight saving is worked out for the date you name. See Timezones |
--idempotency-key <key> | Make a retry safe. The same key with the same request replays the first answer instead of sending it again, for 24 hours. One is generated per invocation when you do not pass one. See Idempotency |
--draft | Save without publishing. Nothing is sent, nothing counts against your quota, and anything that would go wrong is printed back |
--json | Output as JSON |
# Schedule a post
chirpie post "Launching tomorrow!" -s "2026-04-01T14:00:00Z"
# Save a draft to finish later
chirpie post "Half an idea" --draft
# Post with an image
chirpie post "Check this out!" -m https://example.com/screenshot.png
# Post to a specific account
chirpie post "Hello!" -a 550e8400-e29b-41d4-a716-446655440000
# Post to several accounts in one call
chirpie post "Hello everywhere!" -a ACCOUNT_A -a ACCOUNT_B
# Give one account its own text
chirpie post "Hello everywhere!" -a ACCOUNT_A -a ACCOUNT_B \
--config '{"ACCOUNT_B":{"text":"Hello, longer version for this one."}}'Put the link in the first comment:
chirpie post "We rebuilt scheduling this week." \
--first-comment "Full write-up: https://example.com/blog/scheduling"A multi-account post prints one line per account, and the call reports a failure for any account the platform refused while the others stay published. See Post to several accounts at once.
Create a Thread
chirpie thread "First post" "Second post" "Third post"| Flag | Description |
|---|---|
-a, --account <id> | Account ID. Repeat it to publish the thread to several accounts at once |
--config <json-or-file> | Per-account overrides, as JSON or a path to a JSON file. Each value takes posts, which replaces the whole thread for that account, plus first_comment and configuration. A thread publishes to the feed, so an Instagram story or reel, and a Facebook Page story, are refused: each is a single post |
--first-comment <text> | Post this as a comment under the thread's last post, as soon as it goes out. X, Threads, Instagram and Facebook only. It counts as one post against your monthly quota |
-s, --schedule <datetime> | Schedule the thread. An ISO 8601 timestamp carrying a timezone, or a local time with no offset read in --timezone or the timezone saved on your account |
--timezone <iana> | The IANA timezone a --schedule with no offset is read in, such as America/New_York. See Timezones |
--idempotency-key <key> | Make a retry safe. One is generated per invocation when you do not pass one. See Idempotency |
--draft | Save without publishing. A draft thread may be a single post while you are still writing it |
--json | Output as JSON |
Minimum 2 posts, maximum 25, or 1 to 25 with --draft.
A thread is atomic: if any part fails, every part that had already published is
deleted from the platform and the whole thread's quota is refunded. In the rare
case a part could not be removed, the CLI prints it under "Still live on the
platform, delete these yourself" with its link, and the error code is
thread_rollback_incomplete rather than upstream_error, because retrying
would publish it twice.
# The same thread to two accounts, with a longer version for the second
chirpie thread "First post" "Second post" -a ACCOUNT_A -a ACCOUNT_B \
--config '{"ACCOUNT_B":{"posts":[{"text":"a"},{"text":"b"},{"text":"c"}]}}'List Posts
chirpie posts| Flag | Description |
|---|---|
--status <status> | Filter by status: draft, scheduled, publishing, published, failed, deleted |
--account <id> | Filter by account |
--group <id> | Show every post of one multi-account send, by its group ID |
--limit <n> | Number of results (default 20) |
--include-hidden | Include posts you have hidden. Off by default on every filter |
--json | Output as JSON |
A post you deleted stays in the listing, with status deleted: a delete takes
the post down from the platform and never removes your record of it.
Get Post Details
chirpie posts get <post_id>| Flag | Description |
|---|---|
--json | Output as JSON |
Update a Post
Edit a post that has not published yet, or finish a draft.
chirpie posts update <post_id> --text "Now with the typo fixed"
chirpie posts update <post_id> --schedule-at 2027-04-02T09:00:00Z| Flag | Description |
|---|---|
-t, --text <text> | Replacement post text |
-m, --media <files...> | Replacement image or video files on this machine, or public URLs |
--alt <text...> | Replacement alt text, in the same order as --media |
--clear-media | Remove the media from the post |
--first-comment <text> | A new first comment, replacing the one the post carries. Pass "" to remove it. This is also how a first comment is added to a post that has not published yet |
--instagram-placement <placement> | Move the post to another Instagram placement: feed, story or reel. The media is checked again against it. This replaces the whole block, so a queued reel loses its collaborators, cover and trial-reel setting unless you send them again |
--facebook-placement <placement> | Move the post to another Facebook Page placement: feed or story. This replaces the whole block, as above |
--clear-placement | Put the post back to a plain feed post, dropping every publishing option it carries |
-s, --schedule-at <datetime> | New publish time. An ISO 8601 timestamp carrying a timezone, or a local time with no offset read in --timezone or the timezone saved on your account. Omit it to keep the time the post already has. On a draft it queues the post, unless --keep-draft is passed too |
--timezone <iana> | The IANA timezone a --schedule-at with no offset is read in, such as America/New_York |
--keep-draft | Keep a draft a draft, so --schedule-at only changes the time it remembers. Valid on its own as well |
--json | Output as JSON |
Only a post that has not gone out can be edited. Rescheduling one post of a thread moves every part of it.
Publish a Draft
chirpie posts publish <post_id>
chirpie posts publish <post_id> --text "The final wording"| Flag | Description |
|---|---|
-t, --text <text> | Final post text, saved before publishing |
--json | Output as JSON |
Every rule and the monthly quota apply at this point, so a draft that would be refused stays a draft, unchanged. A draft thread publishes whole.
Delete a Post
chirpie posts delete <post_id>| Flag | Description |
|---|---|
--json | Output as JSON (includes cancelled_ids when a queued post is cancelled, and deleted_ids when a draft is deleted) |
On a published post, delete takes it down from the platform and succeeds only
once the platform confirms it is gone. Nothing reaches a platform for a post
that has not gone out: a queued one is cancelled and its quota returned, and a
draft is simply marked deleted, since a draft never counted against anything.
Chirpie keeps the post in every case, marked deleted, so it stays in your
history and in chirpie posts --status deleted. A published TikTok post, a
Facebook Page story, or a post on an Instagram account connected through
Instagram rather than via Facebook, cannot be deleted, so it is refused with
delete_unsupported: delete it in the platform's own app, and hide it here if
you do not want to see it. See the
delete table.
Retry a First Comment
Post a first comment that failed, again. It
re-sends the text the post already carries. That text cannot be changed once
the post is out, so set it while the post is still a draft or still queued with
chirpie posts update <post_id> --first-comment "..." instead.
chirpie posts first-comment <post_id>| Flag | Description |
|---|---|
--idempotency-key <key> | Make a retry safe. The same key replays the first answer instead of sending the comment again, for 24 hours |
--json | Output as JSON (the post, with its first comment now posted) |
A first comment never fails its post, so a published post can be carrying one that did not go out. A retry that works counts as one post against your monthly quota, exactly as the first attempt would have.
Hide a Post
chirpie posts hide <post_id>
chirpie posts unhide <post_id>| Flag | Description |
|---|---|
--json | Output as JSON (includes hidden_ids, every post that moved) |
Hiding takes a post out of your Chirpie listings and nothing else. Nothing
reaches the platform: the post stays exactly as it is, keeps its analytics and
its comments, and unhide puts it back. A thread, or a multi-account send, is
hidden as the one thing it was made as.
Hiding a queued post does not stop it publishing: the scheduler pays no
attention to it. Use chirpie posts delete to stop a scheduled post going out.
List them again with chirpie posts --include-hidden.
Drafts
A draft is saved and nothing else: nothing reaches the platform and nothing counts against your quota until you promote it. Saving one prints, one per line, anything that would go wrong if it were sent as it stands.
# Save it
chirpie post "Half an idea" --draft
# See what is saved
chirpie posts --status draft
# Change the time it remembers, still a draft
chirpie posts update <post_id> --schedule-at 2027-04-02T09:00:00Z --keep-draft
# Queue it
chirpie posts update <post_id> --schedule-at 2027-04-02T09:00:00Z
# Or send it now
chirpie posts publish <post_id>Comments
List Comments on a Post
chirpie comments list <post_id>| Flag | Description |
|---|---|
--limit <n> | Comments to show (default 25, max 100) |
--cursor <cursor> | The next_cursor from the previous page |
--since <datetime> | Only comments posted after this ISO 8601 time |
--no-sync | Show the stored comments without refreshing from the platform |
--include-hidden | Include comments you have hidden |
--include-deleted | Include deleted comments, with the last text Chirpie stored for them |
--json | Output as JSON |
The table shows a short comment ID, the author, the time, the comment text, its state
(hidden or deleted, blank for a live comment), and a tick under replied for a comment
you have already answered. The author is the display name, falling back to the handle;
your own replies show the connected account's name. A deleted comment keeps its text. When
the stored comments could not be refreshed, the reason is printed above the table.
Reply to a Comment
chirpie comments reply <post_id> <comment_id> --text "Yes, that works."| Flag | Description |
|---|---|
--text <text> | The reply (required) |
--idempotency-key <key> | Make a retry safe. The same key with the same reply replays the first answer instead of sending it again, for 24 hours |
--json | Output as JSON |
A reply is published to the platform like any other post, so it counts as one post against your monthly quota.
Hide a Comment
chirpie comments hide <post_id> <comment_id>
chirpie comments hide <post_id> <comment_id> --unhide| Flag | Description |
|---|---|
--unhide | Show a comment you hid earlier |
--json | Output as JSON |
Facebook, Instagram and Threads support hiding. Everywhere else the command reports that the platform has no hide rather than pretending it worked.
Delete a Comment
chirpie comments delete <post_id> <comment_id>| Flag | Description |
|---|---|
-y, --yes | Skip the confirmation prompt |
--json | Output as JSON |
On Facebook and Instagram you can delete any comment on a post you published. Everywhere else you can delete only replies you wrote. On Facebook, Instagram, Threads and LinkedIn the replies under a deleted comment are deleted with it; see what happens to the replies.
A delete cannot be undone, so it asks first. In a script or any other non-interactive
shell there is nobody to ask, so pass -y or the command stops without deleting anything.
The post and comment IDs can be the short ones the tables show, or the full ones from
--json. See IDs.
Accounts
List Accounts
chirpie accountsShows a table of all connected accounts with platform, username, display name, scheduled post count, and active status.
Inactive accounts are listed too, with a ✗ under active. An account marked
over plan limit is connected but not publishing because your plan's account limit was
already full when it was connected. Activate it below, or upgrade.
Activate or Deactivate an Account
chirpie accounts activate <id>
chirpie accounts deactivate <id>| Flag | Description |
|---|---|
--json | Output as JSON (deactivate includes canceled_posts) |
Use the short account ID chirpie accounts shows, or the full one from
chirpie accounts --json. --json may go before or after the
account ID: chirpie accounts deactivate <id> --json and
chirpie accounts --json deactivate <id> are equivalent.
Chirpie never discards a connected account when your plan runs out of room. One Facebook authorization can grant several Pages at once; Chirpie stores every Page and switches on as many as your plan allows, leaving the rest inactive. These two commands choose which ones publish. Deactivating keeps the account connected and frees a plan slot, so you can activate a different one without reauthorizing.
Deactivating cancels the account's scheduled posts. A deactivated account cannot
publish, so its whole queue is cancelled and returned to your monthly quota. The command
prints how many. The scheduled column in chirpie accounts shows the number in advance.
Activating the account again does not bring them back.
# Park one Page and switch on another
chirpie accounts deactivate 550e8400-e29b-41d4-a716-446655440000
chirpie accounts activate 7c9e6679-7425-40de-944b-e07fc1f90ae7
# Read the result from a script
chirpie accounts deactivate 550e8400-e29b-41d4-a716-446655440000 --json | jq '.canceled_posts'Activating past your plan's account limit fails with a plan-limit error.
Disconnect an Account
chirpie accounts disconnect <id>| Flag | Description |
|---|---|
-y, --yes | Skip the confirmation prompt |
--json | Output as JSON (includes canceled_posts) |
Ends the connection. The account stops publishing immediately and stops counting against your plan's account limit, and Chirpie lets go of the credential it stored for it.
A disconnect cannot be undone, so it asks first. In a script or any other non-interactive
shell there is nobody to ask, so pass -y or the command stops without disconnecting
anything.
Disconnecting cancels the account's scheduled posts. Every post still queued against
it is cancelled and returned to your monthly quota. The command prints how many, and the
scheduled column in chirpie accounts shows the number in advance. Connecting the
account again does not bring them back.
# Ask first, then disconnect
chirpie accounts disconnect 550e8400-e29b-41d4-a716-446655440000
# From a script
chirpie accounts disconnect 550e8400-e29b-41d4-a716-446655440000 -y --json | jq '.canceled_posts'Disconnect when you are done with the account. Deactivate instead when you mean to switch it on again: deactivating keeps the stored credential, so activating needs no reauthorization, while connecting a disconnected account again means authorizing it on the platform again. Posts the account already published are kept either way.
Connect an X Account
chirpie accounts connect-xPrints an authorization URL. Open it in your browser to complete the X OAuth flow.
Every connect-* command accepts --json, which prints the raw result instead of the
human-readable line.
Connect a Bluesky Account
chirpie accounts connect-bluesky --handle <handle> --app-password <password>| Flag | Description |
|---|---|
--handle <handle> | Your Bluesky handle. The first part alone is enough (yourname), and yourname.bsky.social, a handle on your own domain, or the email on your account also work. |
--app-password <password> | A Bluesky app password, shaped xxxx-xxxx-xxxx-xxxx. Create one in Bluesky under Settings, Privacy and Security, App Passwords. Omit the flag to be prompted for it without echo. |
--json | Output as JSON |
# Example
chirpie accounts connect-bluesky --handle mybot --app-password xxxx-xxxx-xxxx-xxxxAnything not shaped like an app password, your account password included, is
refused before any request is made, with bluesky_app_password_required and
the same message the API gives. The value is never printed.
Connect a LinkedIn Account
chirpie accounts connect-linkedin
chirpie accounts connect-linkedin --pages # the LinkedIn Pages you administer (coming soon)Prints an authorization URL. Open it in your browser to complete the LinkedIn OAuth flow.
Connect a Threads Account
chirpie accounts connect-threadsPrints an authorization URL. Open it in your browser to complete the Meta OAuth flow for Threads.
Connect a Mastodon Account
chirpie accounts connect-mastodon --instance <instance>| Flag | Description |
|---|---|
--instance <instance> | Your Mastodon server, handle or profile URL: mastodon.social, @you@mastodon.social and https://mastodon.social/@you all work. Only the server is kept. |
--json | Output as JSON |
# Examples: all three connect you on mastodon.social
chirpie accounts connect-mastodon --instance mastodon.social
chirpie accounts connect-mastodon --instance @you@mastodon.social
chirpie accounts connect-mastodon --instance https://mastodon.social/@youA value naming no server is refused before any request with
mastodon_instance_invalid.
Prints an authorization URL. Open it in your browser to complete the Mastodon OAuth flow.
Connect an Instagram Account
chirpie accounts connect-instagram
chirpie accounts connect-instagram --via facebook
chirpie accounts connect-instagram --via facebook --reconnect| Flag | Description |
|---|---|
--via <route> | How to connect: instagram (the default) signs in with Instagram, facebook signs in with Facebook and connects the Instagram accounts linked to the Pages you share. Any other value is refused |
--reconnect | On --via facebook, ask Facebook again about anything that was turned down last time |
--json | Output as JSON |
Prints an authorization URL. Open it in your browser to finish the flow.
Both routes need an Instagram professional account (Business or Creator),
and both publish, thread, schedule and report analytics identically. They
differ in one way: on an account connected with --via facebook,
chirpie posts delete <id> really takes a published post down, and on an
account connected the default way it is refused with delete_unsupported.
--via facebook also needs the Instagram account to be linked to a Facebook
Page, and one run can connect several accounts at once, one per linked Page;
anything past your plan's account limit is stored inactive (shown as
over plan limit) rather than dropped, so use chirpie accounts activate <id>
to pick.
Connecting an account you already have through the other route moves it
rather than adding a second one: it keeps its id and everything it has
published, and only what it can do changes, so going to --via facebook adds
deleting and coming back takes it away again. Where the two routes report
different Instagram accounts you get a second account instead, so run
chirpie accounts afterwards to see what you have. See
switching between the two routes.
Connect a Facebook Page
chirpie accounts connect-facebook
chirpie accounts connect-facebook --reconnectPrints an authorization URL. Open it in your browser to complete the Facebook OAuth flow. You choose which Pages to grant.
Use --reconnect when the account has authorized Facebook before, so Facebook asks again
about anything that was turned down last time.
If the flow finishes and no Page was imported, Facebook did not share one. Run it again and, in the Facebook window, choose Edit settings, tick every Page you want Chirpie to post to, and leave Show a list of the Pages you manage and Create and manage content on your Page turned on. Pages already connected stay connected while you retry.
Every granted Page is imported as its own account. If you grant more Pages than your plan's
account limit allows, Chirpie switches on as many as it can and stores the rest inactive
(shown as over plan limit in chirpie accounts), so nothing is dropped. Use
chirpie accounts activate <id> to pick which Pages publish. Running this command again
re-runs the import, so Pages added later are picked up without disconnecting anything.
Connect a Telegram Bot
chirpie accounts connect-telegram --bot-token <token> --chat-id <id>| Flag | Description |
|---|---|
--bot-token <token> | Telegram bot token from @BotFather (prompts securely if omitted) |
--chat-id <id> | Channel or group. A bare name is enough (channelname), and @channelname, a https://t.me/channelname link, or the numeric ID also work (prompts if omitted). |
--json | Output as JSON |
More Platforms
Pinterest, TikTok, YouTube, and Google Business Profile are coming soon.
Use Your Own X Developer App
Connect X accounts through your own X app so posts use your X API credits. See Bring your own X keys for the full walkthrough.
# Set or replace your keys (prompts for the secret if --client-secret is omitted)
chirpie accounts x-keys set --client-id "YOUR_CLIENT_ID" --label "Acme social app"
# Check whether your own app is configured
chirpie accounts x-keys status
# Remove your keys
chirpie accounts x-keys remove| Flag | Description |
|---|---|
--client-id <id> | OAuth 2.0 Client ID from developer.x.com (required for set) |
--client-secret <secret> | OAuth 2.0 Client Secret (prompts securely if omitted) |
--label <label> | Optional name for the app |
--json | Output raw JSON |
After setting keys, run chirpie accounts connect-x to move an account onto your
app. Accounts you don't reconnect keep using whichever app connected them.
API Keys
List Keys
chirpie keys| Flag | Description |
|---|---|
--json | Output as JSON |
Create a Key
chirpie keys create
chirpie keys create -n "My Bot Key"
# A narrower key: repeat --scope once per permission
chirpie keys create -n "Publishing bot" --scope posts:write --scope media:write| Flag | Description |
|---|---|
-n, --name <name> | Key name |
--scope <scope> | What the new key may do. Repeat it once per scope. Omit it for a key that can do exactly what the key you are calling with can do. See Scopes |
--json | Output as JSON |
Allowed scopes: posts:read, posts:write, accounts:read, accounts:write,
analytics:read, comments:read, comments:write, media:write, keys:write.
A key can never grant a scope it does not itself hold. chirpie keys prints each key's
scopes, or all for a key with full access.
Revoke a Key
chirpie keys revoke <key_id>| Flag | Description |
|---|---|
--json | Output as JSON |
Analytics
Get Post Metrics
chirpie analytics <post_id>
chirpie analytics <post_id> --refresh| Flag | Description |
|---|---|
--refresh | Ask the platform for the current numbers instead of reading the stored snapshot. Allowed once per post every 5 minutes; past that it reports analytics_refresh_rate_limited and the stored numbers are still one ordinary call away |
--json | Output as JSON |
Displays engagement metrics for a post. Available metrics vary by platform:
- X: impressions, likes, retweets, replies, quotes, bookmarks, and clicks
- Bluesky: likes, reposts, replies, and quotes
- LinkedIn: likes, comments, and shares
- Threads: views, likes, replies, reposts, and quotes
- Mastodon: favourites, reblogs, and replies
- Instagram: views, likes, comments, shares, and saves
- Facebook: views, reactions, comments, shares, and clicks
- Telegram: No analytics available