PostNinja

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.

Every request
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/accountsConnected accounts and their ids
GET/api/v1/postsList posts. Query: from, to (ISO times), status, limit (1–100)
POST/api/v1/postsCreate a post: scheduled, published now, or a draft
GET/api/v1/posts/:idOne post, with per-account status and links
PATCH/api/v1/posts/:idChange a draft or scheduled post (fields you send)
DELETE/api/v1/posts/:idDelete a post from PostNinja
POST/api/v1/mediaUpload 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.

POST /api/v1/posts
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"
  }'
201 Created
{
  "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.

Request body
{
  "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.

Request body
{
  "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.

Request body
{
  "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.

POST /api/v1/media
curl https://postninja.app/api/v1/media \
  -H "Authorization: Bearer $POSTNINJA_KEY" \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg

Errors and limits

Errors use HTTP status codes and a consistent body. The message is written to be shown to a person.

400 Bad Request
{ "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_accountsConnected accounts and their ids
list_postsPosts, filtered by time or status
get_postOne post with status, links and errors
create_postCreate, schedule or publish a post
update_postChange a draft or scheduled post
delete_postDelete a draft or scheduled post
upload_imageUpload a base64 image for use in a post
Claude Code
claude mcp add --transport http postninja https://postninja.app/api/mcp \
  --header "Authorization: Bearer $POSTNINJA_KEY"
Cursor (~/.cursor/mcp.json)
{
  "mcpServers": {
    "postninja": {
      "url": "https://postninja.app/api/mcp",
      "headers": { "Authorization": "Bearer ${env:POSTNINJA_KEY}" }
    }
  }
}
Claude Desktop (claude_desktop_config.json)
{
  "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.”