chirpie
Post to X, Bluesky, LinkedIn, Threads, Mastodon, Instagram, Facebook and Telegram from your terminal. Chirpie handles the OAuth, the token refresh and the media upload, so every platform is the same command.
Install
npm install -g chirpie
Or run it without installing: npx chirpie <command>.
Authenticate
chirpie login
chirpie whoami
Or set a key from the dashboard in the environment:
export CHIRPIE_API_KEY=chirpie_sk_...
CHIRPIE_API_KEY takes precedence over the config file. chirpie auth --key ... stores one non-interactively, and chirpie logout removes it.
Commands
chirpie login / logout / auth / whoami | Manage credentials |
chirpie post <text> | Create a post. -a account (repeat it for several accounts), -m media files or URLs, --alt descriptions, --config per-account overrides, --first-comment a comment published under the post as soon as it goes out, --instagram-placement and --facebook-placement to publish it as a story or a reel instead of a feed post, -s schedule, --timezone the IANA zone a -s with no offset is read in, --idempotency-key to make a retry safe, --draft save without sending |
chirpie thread <post> <post> ... | Create a 2 to 25 post thread, or 1 to 25 with --draft. -a is repeatable, --config takes per-account posts, --first-comment posts a comment under the last part, --timezone and --idempotency-key work as they do on a post, -f takes a JSON payload file |
chirpie posts | List posts. --status, --account, --group, --limit, --include-hidden |
chirpie posts get <id> | Fetch one post |
chirpie posts update <id> | Edit a post that has not published yet, or finish a draft. --text, --media, --alt, --clear-media, --first-comment ("" removes it), --instagram-placement, --facebook-placement, --clear-placement, --schedule-at, --timezone, --keep-draft. Leaving --schedule-at out keeps the time it already has |
chirpie posts publish <id> | Send a saved draft now. -t sets the final text first |
chirpie posts first-comment <id> | Post a first comment that failed, again. It re-sends the text the post already carries. --idempotency-key makes a retry safe |
chirpie posts delete <id> | Take a post down from the platform. Chirpie keeps it, marked deleted. Deleting any post of a scheduled thread cancels the thread. A published TikTok post, or one on an Instagram account connected through Instagram rather than via Facebook, cannot be deleted and refuses with delete_unsupported |
chirpie posts hide <id> / unhide <id> | Hide a post from your Chirpie listings, or show it again. Nothing reaches the platform |
chirpie accounts | List connected accounts, active and inactive |
chirpie accounts activate <id> / deactivate <id> | Choose which accounts publish. Deactivating cancels that account's scheduled posts |
chirpie accounts disconnect <id> | End a connection. Cancels that account's scheduled posts, frees a plan slot, and removes the stored credential, so reconnecting means authorizing again. Asks first; -y skips the prompt |
chirpie accounts connect-x | Start the X OAuth flow and print the URL |
chirpie accounts connect-bluesky | Connect Bluesky with an app password |
chirpie accounts connect-linkedin / connect-mastodon | Start that platform's OAuth flow |
chirpie accounts connect-threads / connect-instagram / connect-facebook | Start that platform's OAuth flow (coming soon) |
chirpie accounts connect-instagram --via facebook | Connect Instagram by signing in with Facebook instead, which connects the Instagram accounts linked to the Pages you share and is the route where a published post can be deleted from Chirpie. Add --reconnect to ask again about anything turned down last time (coming soon) |
chirpie accounts connect-linkedin --pages | Connect the LinkedIn Pages you administer, each as its own account (coming soon) |
chirpie accounts connect-telegram | Connect a Telegram bot to a channel or group |
chirpie accounts x-keys set / status / remove | Use your own X developer app |
chirpie analytics <post_id> | Engagement metrics for a published post. --refresh asks the platform now instead of reading the stored snapshot, once per post every 5 minutes |
chirpie keys / keys create / keys revoke <id> | Manage API keys. keys create --scope posts:write --scope media:write mints a key that can do less than yours; chirpie keys prints each key's scopes |
Run chirpie <command> --help for the full option list. Threads, Instagram, Facebook, Pinterest, TikTok, YouTube and Google Business Profile are coming soon. Accounts already connected keep posting and scheduling as normal.
Examples
chirpie post "Shipped a new release."
chirpie post "Look at this" -a 550e8400-e29b-41d4-a716-446655440000 -m ./shot.png --alt "The new dashboard"
chirpie post "Later" -s 2026-04-01T14:00:00Z
chirpie post "Half nine, my time" -s 2026-11-01T09:30 --timezone America/New_York
chirpie post "Shipped" --idempotency-key release-2026-11-01
chirpie thread "One" "Two" "Three" -a 550e8400-e29b-41d4-a716-446655440000
chirpie posts --status scheduled
chirpie post "We rebuilt scheduling this week." \
--first-comment "Full write-up: https://example.com/blog/scheduling"
chirpie posts first-comment "$POST_ID"
Drafts
--draft saves a post or a thread without sending it. Nothing reaches the platform and nothing counts against your quota until you promote it. Anything that would go wrong is printed back, one warning per line.
chirpie post "Half an idea" --draft
chirpie thread "Opening line" --draft
chirpie posts --status draft
chirpie posts update "$POST_ID" --schedule-at 2027-04-02T09:00:00Z --keep-draft
chirpie posts update "$POST_ID" --schedule-at 2027-04-02T09:00:00Z
chirpie posts publish "$POST_ID"
Promoting runs every rule a normal post runs and takes the quota, so a draft that would be refused stays a draft, unchanged. A draft thread is promoted whole.
Several accounts at once
Repeat -a to publish the same post to up to 25 accounts in one call.
chirpie post "Shipped a new release." -a "$X_ACCOUNT" -a "$BSKY_ACCOUNT"
chirpie post "Shipped a new release." -a "$X_ACCOUNT" -a "$BSKY_ACCOUNT" \
--config "{\"$BSKY_ACCOUNT\":{\"text\":\"Shorter, for Bluesky\"}}"
chirpie post "Shipped a new release." -a "$X_ACCOUNT" -a "$BSKY_ACCOUNT" --config ./overrides.json
chirpie thread "One" "Two" -a "$X_ACCOUNT" -a "$BSKY_ACCOUNT" \
--config "{\"$BSKY_ACCOUNT\":{\"posts\":[{\"text\":\"A\"},{\"text\":\"B\"}]}}"
chirpie posts --group 550e8400-e29b-41d4-a716-446655440000
One line is printed per account, with a group ID above them. An account the platform refused is shown with its error while the others stay published, and the command leaves with a non-zero status when any account failed. An override says only what differs, and naming media replaces the shared media for that account.
IDs
Listings print a short ID, the first eight characters of the full one, and every command that takes an account, post, key or comment ID accepts it: chirpie accounts disconnect 90208e20, chirpie posts delete 2e7e8e95, -a 90208e20 when posting. A short ID has to name exactly one of your accounts, posts, keys or comments; when it matches none or several, the command stops and says so, and the full ID from --json always works.
JSON output
Every command accepts --json and prints the raw API response, which is what you want in a script:
chirpie accounts --json | jq -r '.[0].id'
chirpie posts --status scheduled --json | jq length
Put --json on the command you are actually running. For a subcommand, both placements work: chirpie accounts deactivate <id> --json and chirpie accounts --json deactivate <id> are equivalent.
Documentation
Also available: the TypeScript SDK and the MCP server.
MIT