Skip to main content
Create a post targeting one or more accounts; PlugKit publishes to each and returns a per-target result.

Fields

The response carries a target per account with its status and, once live, the platformPostUrl.

Media

mediaItems is the shape to use — it carries the type, the alt text and a custom video thumbnail, none of which a bare URL can express:
Media must be reachable by the platform itself — Instagram and Facebook fetch the file from the URL you give. A link that expires, or one that serves a web page instead of the file (WeTransfer, Drive, Dropbox share links), fails at publish time, not at create time.

The media library

Files you upload stay in the account’s media library, whichever way they got in — the dashboard composer, an upload link, an import from a URL, or the API. List them with GET /media, and pass their ids to a post as mediaIds instead of copying URLs around:
An assistant does the same through list_media, which is what lets someone say “publish the reel I just uploaded” without pasting anything. DELETE /media/:id removes a file for good; it is refused while a post that has not been published yet still uses it.

Uploading a file

No public URL? Two ways in, depending on size. Small files, straight through the API:
Anything bigger — a one-shot upload ticket. Ask for a link, then send the bytes to it directly. The file never transits the API, so size is bounded by storage rather than by request memory:
Use mediaUrl (not uploadUrl) in mediaItems. The ticket is single-use and expires. No terminal? Hand the ticket to a person. The same address opened in a browser (uploadPageUrl, returned alongside uploadUrl) is a page where they drop the file — it goes from their browser straight to storage. This is the way in when an assistant drives PlugKit from a web chat: it has no shell to run the curl, and attaching the video to the conversation instead runs into the chat client’s own attachment limit (30 MB on claude.ai), which is not PlugKit’s. Opening the page doesn’t consume the ticket; only the upload does.

Per-platform overrides

One idea rarely fits every network verbatim — 280 characters on X, a vertical video on TikTok. platforms lets one post carry different content per target, with customContent and customMedia overriding the defaults where they’re given:

What each platform expects

WhatsApp doesn’t do posts — see Messaging.

Threads

Threads is a Meta platform, but it is not Instagram: it has its own app credentials, its own authorization screen and its own threads_* permissions. Nothing carries over from a connected Instagram account.
  • 500 characters, and Threads counts emoji as UTF-8 bytes. A longer content is rejected up front rather than silently truncated.
  • A carousel takes 2 to 20 items. The caption belongs to the post, not to the individual media.
  • allowComment: false closes replies to mentioned accounts only — that is the strictest setting Threads offers; there is no “nobody”.
  • No stories, no drafts, no privacy levels. A Threads post is public. Those options are refused rather than accepted and ignored.
  • No messaging at all. Threads has no DM API, so it never appears in the Inbox, in broadcasts, in sequences, or in comment-to-DM automations.
  • Rate limits are the platform’s: 250 published posts per 24 hours.

Instagram stories

Set asStory: true and the post goes to the account’s story instead of the feed. It is the same call, and the same connection — nothing extra to authorise. A story is not a short post, and Instagram is strict about it:
  • It lasts 24 hours, then it is gone. There is no permanent URL, so the target comes back without a platformPostUrl, and the story is left out of Analytics rather than mirrored as a post frozen at zero.
  • It carries no caption. Instagram drops the text, so content becomes optional when every target of the post is an Instagram account. Keep it when the same post also goes to X or LinkedIn — they still need it.
  • One media, and only these formats: a JPEG image (8 MB max), or an MP4/MOV video of 3 to 60 seconds (100 MB max, H.264/HEVC + AAC). A PNG is refused.
  • No stickers, polls, links or mentions. Instagram’s API publishes the media and nothing else; interactive elements only exist in the app.
  • asStory and trialReel are mutually exclusive — a trial only means something in the feed.
Stories count towards the same Instagram quota as feed posts (100 API publications per rolling 24 hours).

Scheduling

Pass scheduledFor instead of publishNow:
A date without an offset is read in timezone. A date carrying its own offset (2027-01-01T09:00:00+01:00) is absolute and timezone is ignored. Publish a scheduled post early, or retry one that failed:
It returns immediately and publishes in the background. Targets that already published are skipped, so a retry never double-posts.

When a target fails

A post to three accounts where one fails comes back partial, not failed — and the failing target carries its own error. The others are live; the fix is to retry that one target, which the publish call above does. Subscribe to post.published, post.partial and post.failed to hear about it without polling — see Webhooks.

See all fields

Open POST /v1/posts in the API Reference to try it live.