Skip to main content

Upload-Post CLI

The official command line for Upload-Post. Publish, schedule and track posts on TikTok, Instagram, YouTube, LinkedIn, Facebook, X, Threads, Pinterest, Bluesky, Google Business, Discord, Telegram and more, from your terminal, your scripts or an AI agent.

npx @upload-post/cli post video -p my-brand --platforms tiktok,instagram,youtube \
-m ./clip.mp4 -t "Launch day" --wait

It is built on the official upload-post SDK and only uses endpoints documented at docs.upload-post.com.

Install​

# run it without installing
npx @upload-post/cli --help

# or install the `upload-post` command globally
npm install -g @upload-post/cli
upload-post --help

Requires Node.js 18 or newer.

Authentication​

Get an API key at app.upload-post.com/api-keys, then:

upload-post login            # asks for the key, checks it, saves it
upload-post whoami # which account and plan the key belongs to

login stores the key in ~/.config/upload-post/config.json (or $XDG_CONFIG_HOME/upload-post/config.json), readable only by you (mode 600). It can also read the key from stdin: echo "$KEY" | upload-post login.

The key is looked up in this order, first match wins:

  1. --api-key <key>
  2. the UPLOAD_POST_API_KEY environment variable
  3. the saved config file

upload-post logout deletes the saved key.

Commands​

CommandWhat it does
login / logoutSave or remove your API key
whoamiAccount email and plan behind the key
profiles [username]Your profiles and the accounts connected to each (and which need reconnecting)
post video|photos|text|documentPublish now, schedule, or add to the queue
status <request_id|job_id>Per-platform result of an upload
scheduledScheduled and queued posts that have not run yet
cancel <job_id>Cancel a scheduled/queued post (its credits are refunded)
historyPast uploads, one row per platform
analytics <profile>Followers, reach, views... per platform
comments list|replyRead and answer comments (Instagram, Facebook, YouTube, LinkedIn, TikTok, X, Threads, Bluesky)
dms list|sendInstagram direct messages

Every command has --help with examples, e.g. upload-post post video --help.

Publishing​

# Video to several platforms, wait for the result
upload-post post video -p my-brand --platforms tiktok,instagram,youtube \
-m ./clip.mp4 -t "Launch day" -d "Longer description for YouTube/LinkedIn" --wait

# Photo carousel (local files and URLs can be mixed)
upload-post post photos -p my-brand --platforms instagram,threads \
-m ./1.jpg ./2.jpg https://cdn.example.com/3.jpg -t "Behind the scenes"

# Text post, scheduled in a timezone
upload-post post text -p my-brand --platforms x,linkedin,threads -t "We just shipped" \
--schedule 2026-10-01T09:00:00 --timezone Europe/Madrid

# Next free slot of the profile's queue
upload-post post text -p my-brand --platforms linkedin -t "Weekly tip" --queue

# LinkedIn document (PDF, PPT, PPTX, DOC, DOCX)
upload-post post document -p my-brand -m ./deck.pdf -t "Q3 results"

Common options:

OptionMeaning
-p, --profile <name>Profile to publish from (or UPLOAD_POST_PROFILE)
--platforms <list>Comma-separated, e.g. tiktok,instagram,x. X is x
-t, --title <text>Title / caption. For text posts, the text itself
-d, --description <text>Longer text used by LinkedIn, Facebook, YouTube and Pinterest
-m, --media <paths or URLs>The video, photos or document
--schedule <ISO 8601>Publish later (up to 365 days ahead)
--timezone <IANA>Timezone for --schedule; UTC by default
--queueAdd to the next free queue slot
--first-comment <text>First comment after publishing
-o, --option key=valueAny platform-specific API field (repeatable, see below)
--idempotency-key <key>Sending the same key again within 24 h never publishes twice
-w, --waitBlock until every platform has a final result
--timeout <seconds>Limit for --wait (default 900)
--dry-runPrint the exact request, send nothing

Reddit is temporarily unavailable in Upload-Post (the API answers 503 reddit_unavailable), so the CLI refuses it up front.

Platform-specific options​

Everything platform-specific goes through --option, with the API field name exactly as written in the docs (video, photos, text, document). Repeat it for several fields; repeat a [] field for arrays. Prefix a value with @ to send a local file.

upload-post post video -p my-brand --platforms tiktok,youtube,instagram -m ./clip.mp4 -t "Demo" \
--option privacy_level=SELF_ONLY \
--option privacyStatus=unlisted \
--option tags[]=demo --option tags[]=launch \
--option media_type=REELS \
--option tiktok_title="Short caption for TikTok" \
--option thumbnail=@./thumb.jpg

Field names are taken as-is, so they follow the docs even where they are camelCase (YouTube's privacyStatus, categoryId...). Check what will be sent with --dry-run before publishing.

Tracking​

upload-post status 1a2b3c4d5e            # request_id from an upload
upload-post status 1a2b3c4d5e --wait # keep polling until it is final
upload-post scheduled -p my-brand
upload-post cancel a1b2c3d4e5f6
upload-post history --limit 20 --status failed

Uploads run asynchronously. Without --wait, post returns as soon as Upload-Post accepts the post and prints its request_id. Scheduled and queued posts return a job_id instead.

Analytics, comments, DMs​

upload-post analytics my-brand                       # every connected platform
upload-post analytics my-brand --platforms instagram,tiktok --json

upload-post comments list -p my-brand --platform youtube --post-id dQw4w9WgXcQ
upload-post comments reply -p my-brand --platform instagram --comment-id 1789 --message "Thanks!"
upload-post comments reply -p my-brand --platform instagram --comment-id 1789 --message "Check your DMs" --private

upload-post dms list -p my-brand
upload-post dms send -p my-brand --recipient-id 17841400123456789 --message "Hi!"

For AI agents​

The CLI is designed to be driven by agents such as Claude Code, Codex or Cursor, as well as by CI jobs.

  • Always pass --json. stdout then carries exactly one JSON document, for success and for errors alike. Progress messages go to stderr.

  • Pass --wait when you publish, so the command returns only when every platform has a final result, with the post URLs:

    upload-post post video -p my-brand --platforms tiktok,instagram -m ./clip.mp4 -t "Hi" --wait --json
    {
    "success": true,
    "request_id": "1a2b3c4d5e",
    "job_id": null,
    "upload": { "success": true, "request_id": "1a2b3c4d5e", "total_platforms": 2 },
    "status": {
    "status": "completed",
    "completed": 2,
    "total": 2,
    "results": [
    { "platform": "tiktok", "success": true, "post_url": "https://www.tiktok.com/@brand/video/..." },
    { "platform": "instagram", "success": true, "post_url": "https://www.instagram.com/reel/..." }
    ]
    }
    }
  • Branch on the exit code, not on text:

    CodeMeaning
    0Success
    1The API returned an error, or could not be reached
    2Invalid command line (unknown flag, missing option, bad value)
    3No API key, or the key was rejected (HTTP 401)
    4The post finished but at least one platform failed
    5--wait timed out; the upload keeps running, check it with status
  • Errors keep the API's own message, plus the HTTP status and the full response:

    { "success": false, "error": { "message": "Invalid API key", "code": "http_401", "exit_code": 3, "status": 401, "response": { "success": false, "message": "Invalid API key" } } }
  • Retrying is safe with --idempotency-key. If a publish times out, rerun it with the same key instead of posting twice.

  • Use --dry-run to show a user exactly what will be published before doing it.

  • Authenticate with UPLOAD_POST_API_KEY in the environment rather than --api-key, so the key does not end up in shell history or logs. Set UPLOAD_POST_PROFILE to skip --profile.

  • Start with upload-post profiles --json to find profile names and the platforms connected to each. An account with reauth_required: true has to be reconnected in the dashboard before it can publish.

Environment variables​

VariablePurpose
UPLOAD_POST_API_KEYAPI key
UPLOAD_POST_PROFILEDefault profile for post, comments and dms
UPLOAD_POST_BASE_URLAPI base URL (default https://api.upload-post.com/api)
XDG_CONFIG_HOMEWhere the config directory lives (default ~/.config)