Create and Schedule Posts via API
Posts are the core resource of the OmniSocials API. A single post can target one or many connected accounts, be a feed post, story, or reel, carry media, be scheduled, and have per-platform overrides.
Minimal request
The smallest valid post is text to one account:
Code
A successful response returns the new post record with "status": "draft". Drafts are NOT published automatically. Call POST /posts/:id/publish to publish, or use POST /posts/create-and-publish to create and publish in one call.
Content
The content field accepts either a string (same text on every selected channel) or an object keyed by channel ID (per-platform text).
Flat string:
Code
Per-platform:
Code
The default key is the fallback for channels without an explicit override. Character limits are enforced per platform. A post that exceeds any selected platform's limit fails validation.
Accounts and channels
Pass one or more account IDs in the accounts array. Get account IDs from GET /accounts. An account ID is tied to a specific channel (e.g. a specific Instagram profile), not just a platform.
Connecting social accounts is done through OAuth in the OmniSocials dashboard, not through the API. Once connected, they are available to every API key in the workspace.
Post types
The type field selects feed post, story, or reel. It defaults to post if omitted.
| Type | Description | Supported platforms |
|---|---|---|
post | Regular feed post | All platforms |
story | 24 hour story | Instagram, Facebook, Snapchat |
reel | Short-form vertical video | Instagram, Facebook, YouTube, TikTok |
Stories and reels require media. Posting a story or reel without media returns 400.
Attaching media
There are two ways to attach media. Both can be combined in the same request.
1. Upload first, then reference by ID. Upload files through POST /media/upload or POST /media/upload-from-url, then pass the returned IDs in media_ids.
Code
2. Inline URLs. Pass external image, video, or PDF URLs in media_urls. The API downloads them for you and attaches them to the post. Maximum 10 URLs, each file up to 100 MB — an oversized file fails with code file_too_large. For larger files (up to 1 GB), upload first via POST /media/upload-from-url and attach the returned media id via media_ids instead. A PDF URL is rasterized into one image slide per page (up to 20) — on LinkedIn it publishes as a swipeable document, elsewhere as an image carousel.
Code
Per-platform media. Both media_ids and media_urls accept either a flat array (same media on every selected channel) or an object keyed by channel ID. A default key inside the object is the fallback for channels without an explicit override. Pass an empty array to opt a channel out of media.
Code
Alt text (accessibility descriptions)
Alt text is a short description of an image, read aloud by screen readers and shown when an image fails to load. Some communities take it seriously — Mastodon users famously run reminder bots for posts without image descriptions — and several platforms accept it natively through their APIs. OmniSocials lets you attach it per media item and delivers it to every platform that supports it.
How to set it
Any media entry can be an object instead of a bare string:
- In
media_urls:{ "url": "...", "alt": "..." } - In
media_ids:{ "id": "...", "alt": "..." }
Strings and objects can be mixed freely in the same array, and both forms work everywhere media is accepted — flat arrays, per-platform objects, and thread parts (x.thread_parts, bluesky.thread_parts, mastodon.thread_parts).
Alt text is per image. Each media item carries its own description — a post with four images can (and should) have four distinct alt texts. Items without an alt simply publish without a description.
Code
Per-platform media objects work the same way, so a channel override can carry its own descriptions:
Code
Where it goes, per platform
| Platform | Behavior |
|---|---|
| Mastodon | Each image or video uploads with its own native media description — 4 images means 4 distinct alts. Visible to screen readers and satisfies the community's alt-text reminder bots. |
| Bluesky | Per-image alt in the post embed (videos too). Bluesky clients surface missing alt text, so descriptions you set here show up exactly like natively-added ones. |
| X | One media-metadata call per photo or GIF. X does not support alt text on videos. A failure to set alt text never fails the publish — the post still goes out, without the description. |
The exception: Pinterest accepts only one pin-level alt_text, even for carousels. An explicit pinterest.alt_text always wins; otherwise the first media item that has an alt becomes the pin's alt text, and other slides' descriptions are ignored by Pinterest. | |
| Everything else | Currently ignored. Safe to send — entries with alt are accepted on every platform and the description is simply not delivered. |
Special cases
- PDF uploads — a PDF entry rasterizes into one slide per page, and every slide inherits the entry's single
alt. - Threads — each part's media carries its own alt, so a 3-part Mastodon thread with one image per status publishes 3 independent descriptions.
- Videos — delivered on Mastodon and Bluesky; not supported by X; Pinterest video pins use the pin-level
alt_text.
Limits and validation
- Maximum 1500 characters per description (Mastodon's default cap). Longer values return
400with codevalidation_error. - Platforms with a lower cap are clamped at publish time: X truncates at 1000 characters.
altmust be a string; whitespace-only values are treated as unset.
Reading it back
Post read-backs return media items as { url, id, channel, alt? } — alt is present only when set, so you can verify descriptions landed (useful when batch-creating posts through the API or an AI agent).
Link previews
To share a URL as a rich preview card (thumbnail + title + description) on platforms that support it, pass link_url on the post body. This is what LinkedIn calls an "article share" and what Facebook calls a "link post". Without it, a URL in the post text just renders as plain text and no preview card is generated.
Code
| Field | Required | Notes |
|---|---|---|
link_url | yes | Must be a valid http(s) URL. Switching the post into link-share mode. |
link_title | no | Used by LinkedIn directly. Facebook ignores it and fetches the page's OG title server-side. |
link_description | no | Same behaviour as link_title. |
link_thumbnail_url | no | Reserved for forward compatibility. Both platforms currently auto-fetch the OG image. |
Supported channels. LinkedIn (linkedin, linkedin_page) and Facebook (facebook). X auto-renders preview cards from any URL in the tweet body, so link_url is not needed there. Other channels (Instagram, TikTok, etc.) ignore link_url and post the text as-is.
Media vs link share. LinkedIn and Facebook both treat media posts and link-share posts as mutually exclusive. If a request includes both media_urls/media_ids and link_url, media wins and link_url is ignored for those channels.
Scheduling
To schedule a post, add scheduled_at as an ISO 8601 timestamp in UTC. OmniSocials publishes within a 1 minute window of the requested time.
Code
Scheduled posts move from draft to scheduled to posted automatically. You can also schedule a post by creating a draft and calling PATCH /posts/:id to set scheduled_at later.
Platform-specific options
Some platforms support fields that do not apply anywhere else (Pinterest board IDs, YouTube privacy settings, TikTok comment controls, etc.). Pass these as top-level keys on the request body, using the channel ID as the key name.
Code
See the Platforms section for a per-platform breakdown of supported options.
Automatic first comment
Instagram, Facebook, LinkedIn, and YouTube can auto-post a first comment the moment the post publishes — handy for keeping hashtags or a link out of the main caption. Set first_comment under the channel's platform key. Each channel takes its own value, so you can vary it per platform in a single request.
Code
| Channel key | Max length | Notes |
|---|---|---|
instagram | 2200 | Feed posts and reels |
facebook | 8000 | Page posts only |
linkedin | 1250 | Personal profile |
linkedin_page | 1250 | Company page |
youtube | 10000 | Video must allow comments |
Not posted for stories. Leave the key out, or pass an empty string, for no first comment. On PATCH /posts/:id, sending an empty string clears a previously set first comment. It is posted automatically right after the post publishes — no separate call needed.
Hashtag sets
Save a group of hashtags once, then apply it to any post by name. Sets are managed with the Hashtag Sets endpoints (GET/POST /hashtag-sets, PATCH/DELETE /hashtag-sets/:id) and applied at create time:
Code
| Field | Meaning |
|---|---|
hashtag_set | Set name, matched case-insensitively (or hashtag_set_id) |
hashtag_placement | caption_append (default) appends the tags to each caption after a blank line; first_comment puts them in the automatic first comment on channels that support it (see the table above) and falls back to the caption elsewhere |
hashtag_platforms | Optional subset of the post's channels, e.g. ["instagram", "tiktok"] |
The tags are merged in once, when the post is created — the post stores plain text, so editing or deleting a set later never changes existing posts. Tags already present in a caption are skipped, and Instagram's 30-hashtag cap is enforced at create time with a hashtag_limit_exceeded error instead of a publish-time failure.
Publishing
Draft posts do nothing until you publish them. Three ways to publish:
- Create and publish in one call. Use
POST /posts/create-and-publishwith the same body as/posts/create. - Publish an existing draft.
POST /posts/:id/publish. No body required. - Schedule it. Set
scheduled_aton creation. The platform publishes automatically at that time.
Successful publish returns "status": "posted" and populates published_urls, an object mapping each platform identifier (linkedin, instagram, etc.) to the live post URL. Only platforms that published successfully appear.
Updating and deleting
Only drafts and scheduled posts can be updated. PATCH /posts/:id accepts any fields from the create body. Posted posts are immutable. To change a live post, delete it and recreate.
DELETE /posts/:id removes the post from OmniSocials. For posted posts, this also attempts to delete the post from the underlying platform (where supported). For scheduled posts, it cancels the scheduled publish.
Post statuses
| Status | Meaning |
|---|---|
draft | Created but not published or scheduled |
scheduled | Waiting to be published at scheduled_at |
posted | Successfully published to every selected channel |
warning | Published to some but not all selected channels (check errors) |
failed | Publish attempt failed on every selected channel |
When status is warning or failed, the errors object maps each failed platform to a user-friendly message, and published_urls holds the ones that succeeded. To retry a warning post, fix the cause and call POST /posts/:id/publish. A fully failed post cannot be re-published — create a new post instead.