Posts

Create a new post. You can attach media by uploading first (media/media_ids) or by passing external URLs (media_urls) which will be downloaded and processed automatically. Both can be combined.

Required media: Instagram and TikTok posts need at least one image/video. Pinterest posts need an image and a board_id. Posts without required media will be rejected with a 400 error.

Stories (type: "story"): 1 to 10 media items. Each item is one slide and publishes as its own story on Instagram and Facebook, in the order given. Per-platform objects can give each platform its own slide list. Videos max 60 s per slide. After publishing, story_slides lists every slide's native id and URL; published_urls keeps the first slide.

Per-platform media caps (enforced at submit, not at publish time):

  • bluesky: max 4 images per post; cannot mix images and video
  • mastodon: max 4 media items per post; cannot mix images and video
  • x: max 4 images per post; cannot mix images and video
  • instagram: max 10 items in a carousel; images and videos can be mixed in one carousel
  • threads: max 10 items in a carousel; images and videos can be mixed in one carousel
  • tiktok: max 35 items per photo post; cannot mix photos and videos in one post
  • pinterest: max 5 images per carousel pin (videos are exempt: they publish as single video pins)
  • facebook, linkedin, linkedin_page: cannot mix images and video in one post (their APIs accept images only or a single video)

Exceeding any cap returns 400 { error: { code: "validation_error", message: "<Platform> allows up to N media items per post. You attached M, please remove M-N." } } (Pinterest wording: "Pinterest carousel pins support a maximum of 5 images. Please remove some images and try again."). The Pinterest carousel aspect-ratio constraint (2–5 same-ratio images) is enforced the same way; see the Pinterest section for the mismatched_slides response shape.

Per-platform video file-size caps (validated via ffprobe; drafts exempt): Mastodon 99 MB · Bluesky 100 MB · Instagram 300 MB · X 512 MB free tier, 16 GB with Premium / Premium+ (in practice bounded by the 1 GB URL-ingest / 100 MB direct-upload limits) · Threads / Reddit 1 GB · Pinterest 2 GB · Facebook / TikTok 4 GB · LinkedIn 5 GB · YouTube 256 GB. Upload requests are capped at 100 MB on top of these: anything bigger is rejected before media processing with code: file_too_large.

Per-platform video duration caps (validated via ffprobe; drafts exempt): Facebook 240 min (Post + Reel; the separate 90 s Reel cap was removed on 2026-09-30) · Instagram 15 min (Post + Reel) · TikTok 10 min (Post + Reel) · YouTube Short 3 min · X 140 s free tier, 125 min when the connected X account has Premium or Premium+ (tier detected automatically; reconnect X after upgrading) · Bluesky 180 s · Threads 5 min · LinkedIn 10 min · Pinterest 15 min · Reddit 15 min · Mastodon (instance-dependent).

Cap violations return 400 { error: { code: "validation_error", message: "<Platform> only allows videos up to <cap>; yours is <duration>. Trim the video or deselect <Platform>." } } (duration) or "This video (<size>) is too large for <Platform>. ..." (size). When multiple platforms reject the same upload, each contributes one sentence to the message. See API_DEVELOPER_GUIDE.md → "Per-Platform Media Limits" and "Per-Platform Video Duration Caps" for the full table with source citations.

X link-post credits: X's API bills posts whose text contains a URL at a premium ($0.20 vs $0.015), and OmniSocials passes that fee through as prepaid credits at X's rate card (20 credits per URL-containing tweet; threads are billed per part that contains a link). Only a link written with http:// or https:// counts; a bare domain (brand.com) or a www. link is free. Credits are purchased in euros: 1 credit = €0.01, top-ups of €5-500 in the dashboard. When a create/schedule targets X and the text contains a URL, the 201 response includes a warnings array entry (code: "x_url_post_credits") with credits_required, the company's current credits_balance, and the enforcement state. At publish time the credits are debited; if the balance can't cover it, the X target alone fails with an INSUFFICIENT_CREDITS message in its error_message (other platforms in the post publish normally) and the post can be retried after topping up in Settings → Organisation → Billing → Credits. Posts without links, analytics, and everything else on X stay free.

Schedule-time gate: every scheduled X link post reserves its cost until it publishes. Scheduling (or publishing) a new X link post that would push the total reserved past the balance is refused with 402 x_credits_insufficient (details carries credits_required, credits_balance, credits_reserved). Drafts are never gated; posts publishing before the enforcement date are never gated.

post/posts/create

Request

  • Base URL
    https://api.omnisocials.com/v1
  • URL
    https://api.omnisocials.com/v1/posts/create
  • Auth
    HTTP bearer

Request body

contentobject required

Post content. Use 'default' for all platforms, or platform-specific keys: instagram, facebook, linkedin, linkedin_page, youtube, tiktok, pinterest, x, threads, bluesky, mastodon, google_business, telegram, discord. Coming soon: reddit, snapchat.

accountsstring[] required

Social accounts to post to. Each entry is either:

  • A bare platform name (e.g. "youtube", "linkedin_page"), or
  • The composite ID returned by GET /v1/accounts in the form "<workspace_id>_<platform>" (e.g. "844008_youtube").

The composite form's workspace prefix must match the workspace your API key belongs to. Mismatched or unknown ids return 400 validation_error with an "Unknown account/platform" message. Always source ids from GET /v1/accounts for the API key in use.

type'post' | 'story' | 'reel'

Post type: 'post' (feed), 'story' (Instagram/Facebook), 'reel' (Instagram/Facebook/YouTube/TikTok). Telegram and Discord have no reels: a reel that names them is still accepted, and they get the video as a normal post; send a video for them alone as a 'post'. A 'reel' must have video media (MP4/MOV); an image is rejected with 400 validation_error at schedule/publish. Use 'post' to share an image. A 'story' takes 1 to 10 media items: each item is one slide, published as its own story in the order given (Instagram and Facebook have no multi-slide story container). More than 10 returns 400 validation_error.

schedule_atstring date-time

ISO 8601 datetime to schedule. Omit for a draft without a date. With draft: true the time is kept but the post stays a draft (a planned draft on the calendar, see draft).

draftboolean

Planned draft. With schedule_at, the post is stored as draft and keeps that time: it shows on the dashboard calendar at that slot with a dashed outline, the scheduler never publishes it, and a person schedules it from the composer (or you do with PATCH /posts/{id} and draft: false). Use it when an agent proposes a slot that a human should confirm. Not allowed with publish_now or approval_workflow_id (400 validation_error). Without schedule_at it changes nothing: that post is a draft already.

media_fitobject

Image fit per channel, for feed images (type post) whose ratio is outside what a channel shows uncropped. auto (the default when omitted) takes the channel's rule: Instagram crops a tall or wide image to 4:5 / 1.91:1 around the subject (Instagram cuts it to that window anyway, and Meta cannot boost a feed post outside it); Pinterest keeps a Pin taller than 2:3 and only reports it. crop crops for that channel, keep sends the original. Every decision is reported in the response warnings with code: "image_fit". The crop is a file of its own for that channel only; default and the other channels keep the original. On PATCH, media_fit alone (no media) re-decides the stored images: keep gives the original back, crop crops an image that was kept.

publish_nowboolean

Set true to publish immediately

link_urlstring uri

URL to share as a rich preview card on platforms that support link-share posts (currently LinkedIn and Facebook). When set on a text post, the URL renders as a preview tile with thumbnail / title / description instead of plain text. Ignored on platforms that don't support link shares (Instagram, TikTok, etc.) and on posts that already have media attached (media takes precedence).

link_titlestring

Optional title for the link-share preview. LinkedIn uses this directly when set. Facebook ignores this: it fetches OG metadata server-side. Omit to let LinkedIn auto-fetch the page title.

link_descriptionstring

Optional description for the link-share preview. LinkedIn uses this when set; Facebook auto-fetches the OG description. Omit to defer to OG metadata.

link_thumbnail_urlstring uri

Optional thumbnail image URL for the preview card. LinkedIn uploads this image as the card thumbnail; without it, LinkedIn uses the page's Open Graph image, and when the upload fails the card has no image. Facebook ignores it and uses the page's Open Graph image.

sourcestring

Self-reported origin of the integration creating this post. Defaults to 'api' if omitted. Use 'zapier', 'make', 'n8n', etc. to identify your integration.

location_idstring

Instagram only. Facebook Place/Page ID to tag the post's location with (e.g. a dealership or storefront). Applied to single-image and carousel feed posts via Instagram's content-publishing location_id. Find a Place's Page ID via Facebook's Pages Search API. Ignored by other platforms. Send an empty string on PATCH to clear it.

collaboratorsstring[]

Instagram only. Up to 3 public Instagram usernames to invite as co-authors (the 'Collab' feature). Applied to image, carousel, and reel posts, not Stories. Invited users get an invitation in the Instagram app; once accepted, the post also appears on their profile and feed. Usernames are case-insensitive and a leading '@' is stripped. Private accounts and non-existent usernames are rejected by Instagram at publish time. Ignored by other platforms. Send an empty array on PATCH to clear collaborators.

hashtag_setstring

Name of a saved hashtag set (see GET /hashtag-sets) to apply to this post, matched case-insensitively. The set's tags are merged in ONCE at create time: the post stores plain text, so later edits to the set never change this post. Tags already present in a caption are skipped (case-insensitive), and hard platform caps (Instagram 30) are enforced with a 400 hashtag_limit_exceeded before the platform can reject the post. Mutually interchangeable with hashtag_set_id.

hashtag_set_idstring

Id of a saved hashtag set to apply. Same behavior as hashtag_set; pass one or the other.

hashtag_placement'caption_append' | 'first_comment'

Where the set's tags land. caption_append (default) appends them to each target platform's caption after a blank line. first_comment puts them in the auto first comment on comment-capable platforms (Instagram, Facebook, LinkedIn Profile/Page, YouTube, TikTok with comments enabled, Telegram), appended after any first_comment you passed explicitly, and falls back to caption_append on platforms without a comment API. Stories always use captions (first comments are never posted on Stories).

hashtag_platformsstring[]

Optional subset of the post's selected platforms to apply the set to (e.g. only ["instagram", "tiktok"]). Defaults to all selected platforms.

approval_workflow_idstring

Id of an approval workflow (see GET /approval-workflows) to route this post through. The post is created with status in_approval and approval_status: pending instead of scheduled: the workflow's first-step approvers are notified (dashboard badge, email, Slack), they review it in the dashboard's Approvals page, and it publishes at schedule_at only once the last step approves it (dashboard, or POST /posts/{id}/approve). A rejection leaves it rejected. Requires schedule_at; cannot be combined with publish_now. Errors: 404 workflow_not_found (not a workflow of this company/workspace), 400 validation_error (missing schedule_at, publish_now set, or the workflow has no approvers on its first step).

Example request

{
  "content": {
    "default": "Hello world!",
    "instagram": "Hello from IG! #awesome"
  },
  "accounts": [
    "instagram"
  ],
  "media": [
    "482913"
  ],
  "media_urls": [
    "https://example.com/photo.jpg"
  ],
  "schedule_at": "2026-10-21T09:00:00Z",
  "draft": true,
  "media_fit": {
    "instagram": "keep"
  },
  "link_url": "https://goodlightai.com/blog/new-feature",
  "link_title": "Our autumn menu",
  "link_description": "Six new dishes, available from today.",
  "link_thumbnail_url": "https://example.com/preview.jpg",
  "video_cover": {
    "type": "frame",
    "thumb_offset": 3000,
    "overrides": {
      "linkedin_page": {
        "type": "custom",
        "cover_url": "https://example.com/cover.jpg"
      }
    }
  },
  "source": "zapier",
  "location_id": "1234567890",
  "collaborators": [
    "therock",
    "kevinhart4real"
  ],
  "user_tags": [
    {
      "username": "therock",
      "x": 0.5,
      "y": 0.8
    }
  ],
  "hashtag_set": "Fitness Brand",
  "hashtag_set_id": "57",
  "hashtag_platforms": [
    "instagram"
  ],
  "approval_workflow_id": "57823504",
  "tags": [
    "Question hook",
    "Talking head"
  ],
  "comment_dm_automation": {
    "trigger": {
      "keywords": [
        "guide"
      ]
    },
    "actions": {
      "dm": {
        "text": "Here is the guide: https://example.com/guide"
      },
      "public_reply": {
        "enabled": true,
        "variations": [
          "Sent you a DM!"
        ]
      }
    },
    "limits": {
      "max_sends": 500
    }
  },
  "pinterest": {
    "board_id": "1234567890123456789",
    "title": "Autumn menu",
    "link": "https://example.com/menu",
    "video_cover": "https://example.com/cover.jpg",
    "alt_text": "A bowl of pumpkin soup on a wooden table",
    "product_tags": [
      "813744226420795884",
      "813744226420795885"
    ],
    "product_tags_result": {
      "requested": 2,
      "tagged": [
        "813744226420795884"
      ],
      "skipped": [
        {
          "pin_id": "813744226420795885",
          "reason": "PIN_IS_PRIVATE"
        }
      ]
    }
  },
  "youtube": {
    "title": "How we plan a week of posts",
    "tags": [
      "social media"
    ],
    "category_id": "22",
    "first_comment": "Full menu at the link in our bio."
  },
  "instagram": {
    "thumb_offset": 3000,
    "cover_url": "https://example.com/cover.jpg",
    "audio_id": "587784541076604",
    "first_comment": "Full menu at the link in our bio."
  },
  "facebook": {
    "first_comment": "Full menu at the link in our bio.",
    "thumb_offset": 3000,
    "cover_url": "https://example.com/cover.jpg"
  },
  "linkedin": {
    "first_comment": "Full menu: https://example.com/menu"
  },
  "linkedin_page": {
    "first_comment": "Full menu: https://example.com/menu"
  },
  "linkedin_poll": {
    "linkedin": {
      "question": "Which feature should we build next?",
      "options": [
        "Bulk upload"
      ]
    },
    "linkedin_page": {
      "question": "Which feature should we build next?",
      "options": [
        "Bulk upload"
      ]
    }
  },
  "tiktok": {
    "title": "Autumn menu",
    "first_comment": "Full menu at the link in our bio."
  },
  "x": {
    "poll": {
      "options": [
        "Yes"
      ]
    },
    "thread_parts": [
      {
        "text": "1/3: our results are in",
        "media_ids": [
          "12345"
        ]
      },
      {
        "text": "2/3: grab your free pick at https://example.com/signup"
      },
      {
        "text": "3/3: and here's the slip",
        "media_ids": [
          "12346"
        ]
      }
    ]
  },
  "bluesky": {
    "thread_parts": [
      {
        "text": "1/3: our results are in"
      },
      {
        "text": "2/3: read the full writeup at https://example.com/post"
      },
      {
        "text": "3/3: thanks for reading! #buildinpublic"
      }
    ]
  },
  "mastodon": {
    "poll": {
      "options": [
        "Yes"
      ]
    },
    "thread_parts": [
      {
        "text": "1/3: our picks are in"
      },
      {
        "text": "2/3: read the full writeup at https://example.com/post"
      },
      {
        "text": "3/3: thanks for reading! #fediverse"
      }
    ]
  },
  "telegram": {
    "pin": true,
    "buttons": [
      {
        "text": "See the menu",
        "url": "https://example.com/menu"
      }
    ],
    "poll": {
      "options": [
        "Yes"
      ],
      "open_period": 86400
    },
    "first_comment": "Full menu at the link in our bio."
  },
  "discord": {
    "silent": true,
    "link_preview": false,
    "buttons": [
      {
        "text": "Book a table",
        "url": "https://example.com/book"
      }
    ]
  },
  "threads": {
    "topic_tag": "Build in public",
    "poll": {
      "options": [
        "Yes"
      ]
    },
    "location_id": "17841400000000000",
    "location": {
      "id": "17841400000000000",
      "name": "Griffith Observatory",
      "address": "2800 E Observatory Rd",
      "city": "Los Angeles",
      "country": "United States"
    },
    "thread_parts": [
      {
        "text": "1/3: our results are in"
      },
      {
        "text": "2/3: read the full writeup: https://example.com/post"
      },
      {
        "text": "3/3: thanks for reading! #buildinpublic"
      }
    ]
  },
  "google_business": {
    "cta": {
      "url": "https://example.com/book"
    },
    "event": {
      "title": "Autumn tasting night",
      "schedule": {
        "startDate": {
          "year": 2026,
          "month": 10,
          "day": 24
        },
        "startTime": {
          "hours": 18
        },
        "endDate": {
          "year": 2026,
          "month": 10,
          "day": 24
        },
        "endTime": {
          "hours": 22
        }
      }
    },
    "offer": {
      "couponCode": "AUTUMN20",
      "redeemOnlineUrl": "https://example.com/offer",
      "termsConditions": "Valid until 31 October 2026."
    }
  }
}

Response

Post created

Example response

{
  "data": {
    "id": "1024",
    "content": {
      "default": "Our autumn menu is here.",
      "instagram": "Our autumn menu is here. #autumnmenu"
    },
    "accounts": {
      "instagram": true,
      "linkedin_page": true,
      "facebook": false,
      "x": false
    },
    "media": {
      "default": [
        {
          "url": "https://cdn.omnisocials.com/file/library-omnisocials/312a658f-2b45-4289-b240-890807221a74/library/1792573200000-k3j9xq_0c9e6b1a-2f4d-4b8e-a7c3-9d1e5f6a2b70.jpg",
          "id": "482913",
          "alt": "A bowl of pumpkin soup on a wooden table"
        }
      ]
    },
    "location_id": "1234567890",
    "collaborators": [
      "partnerbrand"
    ],
    "user_tags": [
      {
        "username": "partnerbrand",
        "x": 0.5,
        "y": 0.8
      }
    ],
    "schedule_at": "2026-10-21T09:00:00.000Z",
    "app_url": "https://app.omnisocials.com/create-post/1024",
    "tags": [
      {
        "id": 12,
        "name": "Question hook"
      }
    ],
    "published_urls": {
      "facebook": "https://www.facebook.com/123456789/posts/987654321",
      "instagram": "https://www.instagram.com/p/Cxyz123ABC/",
      "linkedin": "https://www.linkedin.com/feed/update/urn:li:share:7191234567890",
      "x": "https://x.com/yourhandle/status/1791234567890123456"
    },
    "retry_of": "1023",
    "retries": [
      "1031"
    ],
    "created_at": "2026-10-20T14:32:10.000Z",
    "errors": {
      "facebook": "Already Posted: These photos were already posted.",
      "instagram": "Instagram couldn't accept one of your images. Check that every image is JPEG/PNG, at least 320×320, has an aspect ratio between 4:5 and 1.91:1, and isn't visually flagged by Instagram. Re-upload or replace the offending image and try again."
    },
    "source": "api",
    "updated_at": "2026-10-20T14:35:42.000Z",
    "x": {
      "poll": {
        "options": [
          "Yes"
        ]
      },
      "thread_parts": [
        {
          "id": "6f1d8e2a-4b7c-4d9e-8a3f-2c5b9e7d1a60",
          "text": "1/3: our results are in"
        }
      ]
    },
    "bluesky": {
      "thread_parts": [
        {
          "id": "6f1d8e2a-4b7c-4d9e-8a3f-2c5b9e7d1a60",
          "text": "1/3: our results are in"
        }
      ]
    },
    "mastodon": {
      "poll": {
        "options": [
          "Yes"
        ]
      },
      "thread_parts": [
        {
          "id": "6f1d8e2a-4b7c-4d9e-8a3f-2c5b9e7d1a60",
          "text": "1/3: our results are in"
        }
      ]
    },
    "threads": {
      "poll": {
        "options": [
          "Yes"
        ]
      },
      "topic_tag": "Build in public",
      "location": {
        "id": "17841400000000000",
        "name": "Griffith Observatory",
        "address": "2800 E Observatory Rd",
        "city": "Los Angeles",
        "country": "United States"
      },
      "thread_parts": [
        {
          "id": "6f1d8e2a-4b7c-4d9e-8a3f-2c5b9e7d1a60",
          "text": "1/3: our results are in"
        }
      ]
    },
    "telegram": {
      "link_preview": true,
      "pin": true,
      "buttons": [
        {
          "text": "See the menu",
          "url": "https://example.com/menu"
        }
      ],
      "poll": {
        "options": [
          "Yes"
        ],
        "open_period": 86400
      },
      "result": {
        "chat_id": "-1001234567890",
        "message_ids": [
          "42"
        ],
        "text_message_id": "43",
        "pinned": true,
        "link_public": "https://t.me/brand/42",
        "link_private": "https://t.me/c/1234567890/42",
        "sent_at": "2026-10-21T09:00:07.000Z"
      }
    },
    "discord": {
      "link_preview": true,
      "thread_name": "October menu",
      "buttons": [
        {
          "text": "See the menu",
          "url": "https://example.com/menu"
        }
      ],
      "poll": {
        "options": [
          "Yes"
        ],
        "duration_hours": 24
      },
      "result": {
        "webhook_id": "1290000000000000000",
        "guild_id": "1180000000000000000",
        "channel_id": "1180000000000000001",
        "message_id": "1300000000000000000",
        "url": "https://discord.com/channels/1180000000000000000/1180000000000000001/1300000000000000000",
        "sent_at": "2026-10-22T09:00:03.000Z",
        "warnings": [
          "media_sent_as_links"
        ]
      }
    },
    "instagram": {
      "first_comment": "Full menu at the link in our bio.",
      "first_comment_result": {
        "posted_at": "2026-10-21T09:00:07.000Z"
      }
    },
    "video_cover": {
      "type": "frame",
      "thumb_offset": 3000,
      "overrides": {
        "linkedin_page": {
          "type": "custom",
          "cover_url": "https://example.com/cover.jpg"
        }
      }
    },
    "facebook": {
      "first_comment": "Full menu at the link in our bio.",
      "first_comment_result": {
        "posted_at": "2026-10-21T09:00:07.000Z"
      }
    },
    "linkedin": {
      "first_comment": "Full menu at the link in our bio.",
      "first_comment_result": {
        "posted_at": "2026-10-21T09:00:07.000Z"
      }
    },
    "linkedin_page": {
      "first_comment": "Full menu at the link in our bio.",
      "first_comment_result": {
        "posted_at": "2026-10-21T09:00:07.000Z"
      }
    },
    "youtube": {
      "first_comment": "Full menu at the link in our bio.",
      "first_comment_result": {
        "posted_at": "2026-10-21T09:00:07.000Z"
      }
    },
    "tiktok": {
      "title": "Autumn menu",
      "first_comment": "Full menu at the link in our bio.",
      "first_comment_result": {
        "posted_at": "2026-10-21T09:00:07.000Z"
      }
    },
    "comment_dm_automation": {
      "id": "318",
      "name": "Guide DM",
      "post_id": "1024",
      "instagram_media_id": "17912345678901234",
      "facebook_post_id": "104857600123456_987654321098765",
      "trigger": {
        "keywords": [
          "guide"
        ]
      },
      "actions": {
        "dm": {
          "text": "Here is the guide: https://example.com/guide"
        },
        "public_reply": {
          "enabled": true,
          "variations": [
            "Sent you a DM!"
          ]
        }
      },
      "limits": {
        "max_sends": 500,
        "stop_at": "2026-11-30T23:00:00.000Z"
      },
      "stats": {
        "matched": 120,
        "sent": 112,
        "skipped": 6,
        "failed": 2,
        "last_event_at": "2026-10-21T15:00:12.000Z"
      },
      "created_at": "2026-10-20T14:32:10.000Z",
      "updated_at": "2026-10-20T14:35:42.000Z",
      "platforms": [
        "instagram",
        "facebook"
      ],
      "automations": [
        {
          "id": "318"
        }
      ]
    }
  },
  "warnings": [
    {
      "code": "x_url_post_credits",
      "credits_required": 20,
      "credits_balance": 480,
      "enforced": true,
      "enforce_from": "2026-08-14"
    }
  ]
}

Changes

    • info

      added the new optional request property

    • info

      added the optional property / to the response with the status

    • info

      added the optional property / to the response with the status

    • info

      added the new optional request property /

    • info

      added the optional property // to the response with the status