Upload a Post

Create a post and fan it out to one or more platforms in a single call.

POST/v1/upload

Body parameters

FieldTypeRequiredDescription
captionstringYesPost text. Platforms with a shorter character cap (e.g. Threads at 500 characters) fail that target with caption_too_long rather than truncating.
mediaarray<{ url, type }>NoPublicly reachable HTTPS media URLs. type is "image" or "video". Omit entirely for a text-only post (Threads and X only — Instagram and TikTok require media).
account_idsarray<string>One of account_ids / platformsOne target is created per id, e.g. ["acct_ig_5f8b2c9a1d7e4f30", "acct_ig_9c1a7e2b8f4d4056"]. This is the only way to target more than one connected profile on the same platform in a single call (e.g. two Instagram accounts at once) — get ids from GET /v1/accounts. Preferred over platforms for anything beyond the simplest one-account-per-platform case.
platformsarray<"instagram"|"threads"|"tiktok"|"x">One of account_ids / platformsLegacy convenience: one target per platform, auto-resolved to the workspace's single connected account for it. Fails with 422 account_not_connected if zero or more than one account is connected for a listed platform — use account_ids or the accounts field below to disambiguate instead.
accountsobject<platform, account_id>NoOnly used together with platforms: which connected account to use per platform, e.g. { "instagram": "acct_ig_5f8b2c9a1d7e4f30" }. Still only one account per platform — use account_ids to target several accounts on the same platform.
sandboxbooleanNoInferred from the API key prefix if omitted. Must agree with the key's own mode — see Sandbox Mode.
simulate_errorstringNoSandbox only. Forces every target to fail with the given error code. See Errors.

Request

This example publishes to two Instagram accounts and one Threads account in a single call — only possible with account_ids, since platforms only ever creates one target per platform.

curl -X POST https://api.omnipost.dev/v1/upload \
  -H "Authorization: Bearer $OMNIPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "caption": "New drop is live 🚀",
    "media": [
      { "url": "https://cdn.yourapp.com/campaign/hero.jpg", "type": "image" }
    ],
    "account_ids": [
      "acct_ig_5f8b2c9a1d7e4f30",
      "acct_ig_9c1a7e2b8f4d4056",
      "acct_th_2b7e4f309c1a7e2b"
    ]
  }'

Response

201 Created — the post was accepted and targets are queued. Publishing itself is asynchronous; poll or use a webhook to learn the outcome.

{
  "id": "post_8f3d1a9c2b7e4f01",
  "status": "pending",
  "sandbox": false,
  "caption": "New drop is live 🚀",
  "media": [
    { "url": "https://cdn.yourapp.com/campaign/hero.jpg", "type": "image" }
  ],
  "targets": [
    { "platform": "instagram", "status": "pending", "external_url": null, "error_code": null, "error_message": null, "published_at": null },
    { "platform": "instagram", "status": "pending", "external_url": null, "error_code": null, "error_message": null, "published_at": null },
    { "platform": "threads", "status": "pending", "external_url": null, "error_code": null, "error_message": null, "published_at": null }
  ],
  "created_at": "2026-08-02T00:00:00.000Z",
  "scheduled_at": null
}

Media constraints

Each platform has its own accepted formats, size, and duration limits — see the platform guides for the full breakdown. Media that a given platform rejects fails only that target with 422 unsupported_media_for_platform (as the immediate response, if detected during validation) or an unsupported_media_for_platform target error (if only discoverable after upload) — the rest of the targets are unaffected.

Media must be URL-reachable

OmniPost fetches your media from the URL you provide — it doesn't accept raw multipart file uploads. Host media somewhere publicly reachable over HTTPS (your CDN, S3/R2 with a public read policy, etc.) before calling this endpoint.