Developers
PostNinja API
Schedule posts to X, Instagram, Threads, Facebook, TikTok, YouTube and Pinterest from your own code or automation tools, or connect an AI assistant over MCP and let it plan your posting for you. Everything runs through the same rules as the app, so a post the API accepts is a post that can go out.
Authentication
Create a key in the app under Settings > API, then send it as a bearer token. Keys belong to a workspace and can do anything a member can. Only a hash is stored, so copy the key when you create it.
Authorization: Bearer pn_…Endpoints
Base URL: https://postninja.app. Requests and responses are JSON (except media uploads). Successful responses wrap results in { "data": … }. Times are ISO 8601 in UTC.
| GET | /api/v1/accounts | Connected accounts and their ids |
| GET | /api/v1/posts | List posts. Query: from, to (ISO times), status, limit (1–100) |
| POST | /api/v1/posts | Create a post: scheduled, published now, or a draft |
| GET | /api/v1/posts/:id | One post, with per-account status and links |
| PATCH | /api/v1/posts/:id | Change a draft or scheduled post (fields you send) |
| DELETE | /api/v1/posts/:id | Delete a post from PostNinja |
| POST | /api/v1/media | Upload a photo or video (raw body) |
Create a post
Pick accounts from GET /api/v1/accounts, then send one of scheduledAt, publishNow: true or draft: true.
curl https://postninja.app/api/v1/posts \
-H "Authorization: Bearer $POSTNINJA_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "We just shipped dark mode. Here is the full story.",
"accountIds": ["8c1e…", "f02a…"],
"mediaIds": ["5b9d…"],
"scheduledAt": "2026-10-06T09:00:00Z"
}'{
"data": {
"id": "0d3c…",
"status": "scheduled",
"content": "We just shipped dark mode. Here is the full story.",
"platformContent": {},
"xThread": [],
"instagramFormat": "post",
"scheduledAt": "2026-10-06T09:00:00.000Z",
"media": [{ "id": "5b9d…", "kind": "image", "url": "https://postninja.app/media/5b9d….jpg" }],
"targets": [
{ "accountId": "8c1e…", "platform": "x", "username": "acme", "status": "pending", "url": null, "error": null },
{ "accountId": "f02a…", "platform": "instagram", "username": "acme.studio", "status": "pending", "url": null, "error": null }
]
}
}Once a post goes out, each target's status becomes published with a link in url, or failed with the reason in error.
Different text for each platform
content goes everywhere unless a platform has its own text in platformContent. Use it for a short X version, hashtags on Instagram, or anything else that should differ.
{
"content": "Our biggest update yet: dark mode, faster search and a new editor.",
"platformContent": {
"x": "Dark mode is here 🌙",
"instagram": "Our biggest update yet ✨ #productupdate #darkmode",
"threads": "Dark mode, faster search and a new editor. What should we build next?"
},
"accountIds": ["…"],
"publishNow": true
}Limits are checked per platform: X 280 characters with up to 4 photos or 1 video; Instagram needs 1–10 photos or videos with captions up to 2,200 characters; Threads 500 characters and up to 20 items.
X threads and Instagram Stories
Put follow-up posts in xThread to turn the X version into a thread: the first post is the X text, and each entry replies to the one before (up to 25 posts, 280 characters each, text only). Every post in a thread uses credits. Set instagramFormat to story to post to Instagram as a Story, with exactly one photo or a 3–60 second video; Instagram doesn't show captions on Stories.
{
"content": "We rebuilt our editor from scratch. Here's what changed 🧵",
"xThread": [
"1. It's 3x faster on big documents.",
"2. Everything works offline now.",
"Try it today: https://example.com/editor"
],
"instagramFormat": "story",
"mediaIds": ["…"],
"accountIds": ["…"],
"scheduledAt": "2026-10-06T09:00:00Z"
}TikTok settings
tiktok holds the choices TikTok leaves to the person posting. privacy is one of PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR or SELF_ONLY; left out, the post is public when the account allows it. allowComments, allowDuet and allowStitch default to true (Duet and Stitch only apply to videos). Set yourBrand when the post promotes your own business, or brandedContent for a paid partnership, which can't be private. Posting means you agree to TikTok's Music Usage Confirmation, and to its Branded Content Policy for branded content.
{
"content": "Our new spring collection is here",
"tiktok": { "privacy": "PUBLIC_TO_EVERYONE", "allowComments": true, "allowDuet": false, "allowStitch": false, "yourBrand": true },
"mediaIds": ["…"],
"accountIds": ["…"],
"scheduledAt": "2026-10-06T09:00:00Z"
}Photos and video
Upload the file as the raw request body with its content type, then use the returned id in mediaIds. Photos (JPEG, PNG, WebP, up to 30 MB) are converted to JPEG. Videos (MP4 or MOV, up to 512 MB) are stored as they are; pass durationMs, width and height to have platform limits checked when you create the post.
curl https://postninja.app/api/v1/media \
-H "Authorization: Bearer $POSTNINJA_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpgErrors and limits
Errors use HTTP status codes and a consistent body. The message is written to be shown to a person.
{ "error": { "code": "invalid", "message": "X: posts can be up to 280 characters. This one is 312." } }Codes: unauthorized (401), invalid (400), not_found (404), conflict (409, e.g. changing a post that already went out), too_large (413), rate_limited (429, with Retry-After). Each key can make 120 requests a minute.
MCP for AI agents
PostNinja runs a remote MCP server, so assistants like Claude and Cursor can see your accounts, draft and schedule posts, and check how they did. It uses the same API keys. Server URL: https://postninja.app/api/mcp
| list_accounts | Connected accounts and their ids |
| list_posts | Posts, filtered by time or status |
| get_post | One post with status, links and errors |
| create_post | Create, schedule or publish a post |
| update_post | Change a draft or scheduled post |
| delete_post | Delete a draft or scheduled post |
| upload_image | Upload a base64 image for use in a post |
claude mcp add --transport http postninja https://postninja.app/api/mcp \
--header "Authorization: Bearer $POSTNINJA_KEY"{
"mcpServers": {
"postninja": {
"url": "https://postninja.app/api/mcp",
"headers": { "Authorization": "Bearer ${env:POSTNINJA_KEY}" }
}
}
}{
"mcpServers": {
"postninja": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://postninja.app/api/mcp", "--header", "Authorization:${AUTH_HEADER}"],
"env": { "AUTH_HEADER": "Bearer pn_…" }
}
}
}Then ask in plain words, for example: “Schedule a post for Monday at 9am on X and Threads announcing our webinar, with a shorter version for X.”