Publish

Publish one post to one or many networks now, and check a post's status.

View as MarkdownOpenAPIBase URL https://postwire.io

One request publishes to every network you name, in parallel. Each network gets its own result, so one failing never fails the others. posted counts the networks that went out. Send per_platform drafts (from AI writing) to publish a different native text on each network, and options for the per-network options.

Post (or cross-post) to one or more platforms

POST/api/post

Needs an API key: Authorization: Bearer pw_live_….

Body (JSON)

FieldTypeDescription
platformstringSingle platform; or use 'platforms'.
platformsarray of stringCross-post to many at once, in parallel.
textstringCaption / description.
titlestringTitle (used by YouTube).
video_urlstringURL of the video to post (required for YouTube; TikTok takes a video or photos).
photo_urlstringURL of one image. TikTok posts it as a photo post. Instagram and TikTok take JPEG (TikTok also WebP): PostWire converts PNG, WebP, AVIF and still GIF to JPEG automatically; animated GIF and HEIC are refused.
privacystringe.g. private | public | unlisted (YouTube).
mediaarray of objectSeveral images (or images and videos) in one post, in order. Instagram: a carousel of up to 10 (images and MP4 videos). TikTok: a photo post of up to 35 images. PNG (and WebP, AVIF, still GIF) is converted to JPEG automatically for Instagram and TikTok. Bluesky: up to 4 images. Mastodon: up to 4. LinkedIn: up to 20 images, or one document (PDF/PPT/DOC). Networks that take one item get the first, and the result says so. Also accepted inside a per_platform draft. Up to 35 items.
media[].url requiredstringPublic https URL of the file.
media[].typestringOne of: image, video, document. Default "image".
media[].altstringAlt text (Bluesky, Mastodon, LinkedIn).
media[].titlestringDocument title (LinkedIn).
photo_urlsarray of stringShorthand for media: a list of image URLs.
per_platformobjectPer-network overrides, keyed by network name (e.g. {"tiktok":{"text":"…"},"youtube":{"title":"…","text":"…"}}). Each value may carry text, title, video_url, photo_url, media and network options. X also takes thread: ["…", "…"] — the posts of a thread after text, in order (at most 25), each published as a reply to the one before it; each at most 280 characters as X counts them (a link counts 23, an emoji 2), and an empty, non-text or over-long post, or a 26th, is refused with code bad_x_thread before anything goes out. Media goes on the first post only. Every post of a thread is an X post: it uses X credits by the same rule (1, or 10 with a link) and counts toward the daily X limit; the whole thread is reserved before the first post is sent, so a thread without enough credits is refused whole.
per_platform.xobject
optionsobject (see Network options)Options per network, keyed by network name. See Network options.
brand_idstringBrand whose connected accounts publish this post (GET /api/brands). Defaults to the account's only brand.
idempotency_keystringSend the same key on a retry and the post is not published twice.

Responses

StatusMeans
200One result per network. Check `posted` and each `results[].ok`. Each result carries account { platform, handle, display_name, brand_id, brand, label } and published_to names every account reached. With the same network in more than one brand and no brand_id: 409 ambiguous_brand, nothing published. On a plan with a networks-per-post limit (Free: 2) the first networks that pass their checks are published and the others come back held (code networks_per_post), listed in held_networks with held_message and an upgrade link: a partial publish is a 200 that says what it held back.
202202 = held for approval (the brand's approval rules or a limit on this key): nothing was published. { ok: true, posted: 0, status: "pending_approval", id (the post's PostWire id, for GET /api/post/status), approval_id, approval: { reasons, notified: [{ to (masked), role, via, sent }], review_url (the dashboard; sign-in needed), who_can_approve, publishes }, results: [{ platform, ok: false, status: "pending_approval", code: "pending_approval", account }], message_for_user }. Only a person approves it: the emailed link or a signed-in dashboard session, never an API key or an AI connector.
400Bad request. bad_x_thread: an X thread (per_platform.x.thread) or reply (options.x.reply) post is empty, not text or over 280 characters as X counts them, or the thread has more than 25 posts — nothing was published to any network.
401Missing or invalid API key
403Email not verified, or the key is read-only. 403 also: key_network_not_allowed, key_brand_not_allowed, key_monthly_cap_reached (limits set on this API key or AI app; nothing was published), plan_feature. 503 governance_unavailable: the approval rules could not be checked, nothing was published.
curl
curl -X POST "https://postwire.io/api/post" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "platforms": [
    "bluesky",
    "linkedin",
    "telegram"
  ],
  "text": "Our sourdough now proofs for 36 hours. Here is what changed in the crumb.",
  "idempotency_key": "bakery-2026-10-03-sourdough"
}'
Node
const res = await fetch("https://postwire.io/api/post", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "platforms": [
      "bluesky",
      "linkedin",
      "telegram"
    ],
    "text": "Our sourdough now proofs for 36 hours. Here is what changed in the crumb.",
    "idempotency_key": "bakery-2026-10-03-sourdough"
  }),
});
console.log(res.status, await res.json());
Python
import os, requests

r = requests.post(
    "https://postwire.io/api/post",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
    json={
        "platforms": ["bluesky", "linkedin", "telegram"],
        "text": "Our sourdough now proofs for 36 hours. Here is what changed in the crumb.",
        "idempotency_key": "bakery-2026-10-03-sourdough",
    },
)
print(r.status_code, r.json())

Example response

JSON
{
  "posted": 3,
  "published_to": "Published to @bakery.bsky.social (Bakery) on bluesky, Ana Ruiz (Bakery) on linkedin, chat @bakerynews (Bakery) on telegram.",
  "results": [
    {
      "ok": true,
      "platform": "bluesky",
      "id": "at://did:plc:4x2…/app.bsky.feed.post/3l7…",
      "url": "https://bsky.app/profile/bakery.bsky.social/post/3l7…",
      "account": {
        "platform": "bluesky",
        "handle": "bakery.bsky.social",
        "brand": "Bakery",
        "label": "@bakery.bsky.social (Bakery)"
      }
    },
    {
      "ok": true,
      "platform": "linkedin",
      "id": "urn:li:share:72…",
      "url": "https://www.linkedin.com/feed/update/urn:li:share:72…"
    },
    {
      "ok": true,
      "platform": "telegram",
      "id": "412",
      "url": "https://t.me/bakerynews/412"
    }
  ]
}

Each result also carries account, the connected account it went to. The second and third are shortened here.

Check the status of a created post

GET/api/post/status

With platform and the network's post id: that post's status and link. With the id PostWire returned for a post that waits for approval or is scheduled (a uuid; platform can be omitted): status pending_approval (with the approval: who was asked, review_url), rejected (with the reason and who decided), scheduled (approved_by when it was approved), or the post's status and each network's result once it went out.

Needs an API key: Authorization: Bearer pw_live_….

Parameters

FieldTypeDescription
platformquery, stringThe network. Can be omitted when id is PostWire's id for a waiting or scheduled post.
id requiredquery, string

Responses

StatusMeans
200{ platform, id, status }
curl
curl "https://postwire.io/api/post/status?platform=instagram&id=igp_5d0c%E2%80%A6" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY"
Node
const res = await fetch("https://postwire.io/api/post/status?platform=instagram&id=igp_5d0c%E2%80%A6", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}` },
});
console.log(res.status, await res.json());
Python
import os, requests

r = requests.get(
    "https://postwire.io/api/post/status?platform=instagram&id=igp_5d0c%E2%80%A6",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
)
print(r.status_code, r.json())

Example response

JSON
{
  "platform": "instagram",
  "id": "igp_5d0c…",
  "status": "processing"
}