Postwith API and CLI

Last updated: October 19, 2026

The REST API lets your own code do what your AI does with Postwith: add a photo or video, make a draft with a version for each account, check it, then schedule it. The CLI does the same from a terminal, and OAuth apps let your product post for other Postwith users. Every call acts as one person, with the same rules as the dashboard: YouTube and Pinterest are open now, and Instagram, Facebook Pages and TikTok are added as each finishes reviewing Postwith.

The API is at https://api.postwith.app/v1. Its OpenAPI description works with code generators, Postman and AI tools.

Sign in

Both kinds of token also work with the AI connector at https://mcp.postwith.app/mcp, for MCP clients that send a fixed header.

Quick start

export POSTWITH_API_KEY=pwk_…

# Where you can post
curl https://api.postwith.app/v1/accounts \
  -H "Authorization: Bearer $POSTWITH_API_KEY"

# Add a photo from a public link
curl https://api.postwith.app/v1/media \
  -H "Authorization: Bearer $POSTWITH_API_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://postwith.app/samples/sample-photo.jpg"}'

# Make a draft: nothing is posted yet
curl https://api.postwith.app/v1/posts \
  -H "Authorization: Bearer $POSTWITH_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "mediaId": "MEDIA_ID",
    "caption": "Hello from the API",
    "accountIds": ["ACCOUNT_ID"],
    "when": "2026-10-20T09:00:00-05:00",
    "timezone": "America/Chicago",
    "pinterest": {"boards": [{"accountId": "ACCOUNT_ID", "boardId": "BOARD_ID"}]}
  }'

# Check the versions it returns, then schedule it
curl -X POST https://api.postwith.app/v1/posts/POST_ID/schedule \
  -H "Authorization: Bearer $POSTWITH_API_KEY"

Every answer is {"data": …, "summary": "…"}: the data, and the same plain-English summary your AI would read.

Endpoints

CallWhat it does
GET /accountsYour connected accounts, or practice mode, and whether each can post now
GET /accounts/{id}/boardsA Pinterest account’s boards
GET /mediaYour photos and videos, newest first
POST /mediaAdds one from a public https link: {"url": …}
POST /media/upload-linksA one-time link (24 hours) for a file on a device
GET /media/upload-links/{id}Whether that file has come in, with its mediaId
GET /postsRecent posts and where each stands
POST /postsMakes a draft, with a version for each account. Sending the same draft twice returns the same post
GET /posts/{id}A post’s versions and how each network went, with links once it’s live
POST /posts/{id}/scheduleSends a draft out at its time, or within a minute if it’s for now. Safe to repeat
POST /posts/{id}/cancelStops a scheduled post. Anything already live stays live
GET /posts/{id}/commentsThe comments on its Instagram and Facebook versions, newest first, read live and not kept. next goes back as cursor for older ones. With your API key, not an OAuth app
POST /posts/{id}/comments/{commentId}/repliesAnswers a comment publicly from the account: {"text": …}. Send only what the account’s owner approved. The same reply again returns the first one
GET /results?days=30How your Instagram, Facebook and TikTok posts did over 7, 30 or 90 days
GET /streakYour posting streak and milestones

Drafts

Files from a device

For a file that has no public link, make an upload link, then send the file to it. The upload token is the last part of the link (pwu_…); it’s the permission, so these steps need no API key.

POST /media/upload-links                → {"link": "https://app.postwith.app/u/pwu_…", "uploadId": …}
POST /uploads/{token}/start             {"name": "clip.mp4", "type": "video/mp4", "size": 12345678}
                                        → {"data": {"uploadUrl": …, "contentType": "video/mp4"}}
PUT  {uploadUrl}                        the file's bytes, with that Content-Type
POST /uploads/{token}/finish            → {"data": {"mediaId": …, "type": "video"}}

Or open the link on any device, or run npx postwith upload <file> <link>.

Errors and limits

A refusal is {"error": {"code", "message", "requestId", "retryable"}}, and its message says what to change in plain words.

StatusCodeWhen
400invalid_requestSomething’s missing or not allowed
401unauthorizedNo key or token, or it was revoked
403forbiddenThe person can’t do this in their workspace
404not_foundNot found
409conflict, limit_reachedNot possible right now, or a limit was reached (like 10 accounts)
422not_readyA network can’t take the draft yet; the message lists what to fix
429rate_limitedOver 120 requests a minute for one key or app; wait for Retry-After
503unavailablePostwith couldn’t do it right now; try again

Each person can have 10 API keys and 5 OAuth apps.

OAuth apps

An OAuth app lets your product post for other Postwith users. Each person approves it on Postwith’s own page, sees it in their Settings, and can remove it anytime. Your app gets a token that does only what that person could.

  1. Register it in Settings › Developers: a name people will see, your redirect URLs (https, or http://localhost while you build) and your website. Copy the client ID and secret; the secret is shown once, and you can rotate it.
  2. Send the person to Postwith with PKCE (S256):
    https://mcp.postwith.app/oauth/authorize?response_type=code
      &client_id=pwc_…&redirect_uri=https://yourapp.example/postwith/callback
      &code_challenge=…&code_challenge_method=S256&state=…
    They sign in and choose Allow. Postwith sends them back with code, your state and iss=https://mcp.postwith.app.
  3. Swap the code from your server, with your secret as HTTP Basic (or client_id and client_secret in the form):
    curl https://mcp.postwith.app/oauth/token -u "pwc_…:pws_…" \
      -d grant_type=authorization_code -d code=… -d code_verifier=… \
      -d redirect_uri=https://yourapp.example/postwith/callback
    You get an access_token for an hour and a refresh_token for 30 days.
  4. Call the API with Authorization: Bearer <access_token>, and get a new one with grant_type=refresh_token and your secret. Refresh tokens rotate: use each once, because reusing one ends that person’s connection.

Only a workspace’s owners and admins can approve an app. Deleting your app ends every connection to it. Keep the secret on your server, never in an app people download. Comments aren’t open to OAuth apps: reading and answering them works with a person’s own API key or in their AI chat, where they OK each reply.

CLI

npx postwith login                 # paste a key from Settings › Developers
npx postwith accounts
npx postwith post photo.jpg --caption "Autumn menu" --to pinterest --board "Menus" --at "tomorrow 9am"
npx postwith post clip.mp4 --caption "Weekend recap" --to youtube --title "Weekend recap" \
  --youtube-privacy public --made-for-kids no --at +2h
npx postwith post soup.jpg salad.jpg cake.jpg --caption "Three new dishes" --to instagram,facebook
npx postwith post tonight.jpg --caption "Tasting tonight at 7" --to instagram,facebook --instagram-story
npx postwith posts
npx postwith show <postId>
npx postwith cancel <postId>
npx postwith comments <postId>
npx postwith reply <postId> <commentId> "Thank you!"

post takes a file on your computer, a public link or a mediaId (or up to 10 photos, which make one post in that order), shows every version, and schedules it after you say yes; --instagram-story makes it a Story on each Instagram account in --to; --yes skips the question and --draft stops at the draft. reply shows the comment and your reply, and asks before posting it. --at takes now, +30m, +2h, tomorrow 9am, 2026-10-20 18:00 or an ISO 8601 time. For CI, set POSTWITH_API_KEY instead of logging in. Every command takes --json; npx postwith lists them all.

Help

Email hi@postwith.app. See also our privacy policy and terms.