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
- An API key, for your own scripts and tools: make one in Postwith › Settings › Developers and send it as
Authorization: Bearer pwk_…. A key acts as you in your workspace, so keep it secret, and make one per script so you can revoke it on its own. - An OAuth app, when other people use your product: they approve it on Postwith’s own page, and your app gets a token for each of them. See OAuth apps.
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
| Call | What it does |
|---|---|
GET /accounts | Your connected accounts, or practice mode, and whether each can post now |
GET /accounts/{id}/boards | A Pinterest account’s boards |
GET /media | Your photos and videos, newest first |
POST /media | Adds one from a public https link: {"url": …} |
POST /media/ | A one-time link (24 hours) for a file on a device |
GET /media/ | Whether that file has come in, with its mediaId |
GET /posts | Recent posts and where each stands |
POST /posts | Makes 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}/ | Sends a draft out at its time, or within a minute if it’s for now. Safe to repeat |
POST /posts/{id}/ | Stops a scheduled post. Anything already live stays live |
GET /posts/{id}/ | The 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}/ | Answers 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? | How your Instagram, Facebook and TikTok posts did over 7, 30 or 90 days |
GET /streak | Your posting streak and milestones |
Drafts
whenis"now"or an ISO 8601 time with its offset, up to a year ahead. Sendtimezone(likeAmerica/Chicago) so results and streaks follow your day.- YouTube needs a
title(up to 100 characters) andyoutube:whoCanWatch(public, unlisted or private) andmadeForKids. YouTube asks every uploader, so these must be the person’s own answers. A square or vertical video of up to 3 minutes becomes a Short. - Pinterest needs a board for each Pinterest account, from
GET /accounts/{id}/boards, and can take alinkthe Pin leads to. - TikTok, once it’s open, needs
tiktok.whoCanView, chosen by the person. One photo or more make a TikTok photo post, which shows thetitle(up to 90 characters there) and gets a sound TikTok picks, which the person can change in TikTok;"autoAddMusic": falseintiktokleaves it out. Its version comes back with"format": "photo"andautoAddMusic. - Practice mode stands in when no network is connected: its posts go through Postwith and nothing goes out publicly.
- One video, or up to 10 photos, per post.
mediaIdis the video or the first photo, andmoreMediaIdslists the other photos in the order they show (up to 9). Several photos make a carousel on Instagram, one post on Facebook and a photo post on TikTok; YouTube takes one video, and Pinterest one photo or video. - Instagram Stories: Instagram accounts listed in
instagram.storyAccountIdsget the photo or video as a Story instead of a post or Reel. A Story is one photo or one video of 3 to 60 seconds, shows without the caption (the other accounts still get it) and disappears after 24 hours. Its version comes back with"format": "story"and"caption": null. - Photos: JPEG, PNG or WebP up to 20 MB, saved as JPEG. Videos: MP4 or MOV up to 50 MB.
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.
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | Something’s missing or not allowed |
| 401 | unauthorized | No key or token, or it was revoked |
| 403 | forbidden | The person can’t do this in their workspace |
| 404 | not_found | Not found |
| 409 | conflict, limit_reached | Not possible right now, or a limit was reached (like 10 accounts) |
| 422 | not_ready | A network can’t take the draft yet; the message lists what to fix |
| 429 | rate_limited | Over 120 requests a minute for one key or app; wait for Retry-After |
| 503 | unavailable | Postwith 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.
- Register it in Settings › Developers: a name people will see, your redirect URLs (https, or
http://localhostwhile you build) and your website. Copy the client ID and secret; the secret is shown once, and you can rotate it. - Send the person to Postwith with PKCE (S256):
They sign in and choose Allow. Postwith sends them back withhttps://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=…code, yourstateandiss=https://mcp.postwith.app. - Swap the code from your server, with your secret as HTTP Basic (or
client_idandclient_secretin the form):
You get ancurl 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/callbackaccess_tokenfor an hour and arefresh_tokenfor 30 days. - Call the API with
Authorization: Bearer <access_token>, and get a new one withgrant_type=refresh_tokenand 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.