{"openapi":"3.1.0","info":{"title":"Outstand Posts v1","description":"# Posts API\n\nCreate, schedule, and manage social media posts across multiple platforms simultaneously through a single unified API.\n\n## Key Concepts\n\n**Posts** represent content to be published to one or more social media platforms. Each post uses a container model where individual containers hold content for each segment (e.g., tweets in an X thread, carousel items).\n\n**Scheduling** is controlled via the `scheduledAt` field (ISO 8601). Omit it to publish immediately.\n\n**Media** can be attached to posts by referencing `mediaIds` from the Media API.\n\n## Supported Platforms\n\nX (Twitter), Threads, LinkedIn, Instagram, Bluesky, YouTube, Facebook, TikTok, Google Business Profile, Pinterest, Vimeo, and Reddit.","version":"1.0.0"},"servers":[{"url":"https://api.outstand.so"}],"components":{"schemas":{"LegacyNormalizedReply":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the reply","example":"x_123456789_1704067200000"},"author":{"type":"string","description":"Author of the comment as the network identifies them (username, display name, or platform URN). Unchanged by `resolve_author_names`.","example":"johndoe"},"text":{"type":"string","description":"Reply content","example":"Great post!"},"created_at":{"type":["string","null"],"description":"ISO timestamp when the reply was created","example":"2025-01-15T10:30:00Z"},"like_count":{"type":"number","description":"Number of likes on the comment, when the platform reports it","example":5},"platform_specific":{"description":"Platform-specific data","example":{}},"replies":{"type":"array","items":{"$ref":"#/components/schemas/LegacyNormalizedReply"}}},"required":["id","author","text","created_at"],"deprecated":true},"LegacyFacebookReplies":{"type":"object","properties":{"comments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"},"timestamp":{"type":["string","null"]},"from":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"like_count":{"type":"number"}},"required":["id","text","timestamp"]}}},"required":["comments"],"description":"Deprecated Facebook-only shape. Use `data` instead."},"LegacyInstagramReplies":{"type":"object","properties":{"comments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"},"timestamp":{"type":["string","null"]},"username":{"type":"string"},"like_count":{"type":"number"},"replies":{"description":"Raw Instagram Graph replies edge, or an empty array when the comment has none"}},"required":["id","text","timestamp"]}}},"required":["comments"],"description":"Deprecated Instagram-only shape. Use `data` instead."},"LegacyBlueskyReply":{"type":"object","properties":{"id":{"type":"string"},"author":{"type":"string"},"author_display_name":{"type":"string"},"author_avatar":{"type":"string"},"text":{"type":"string"},"created_at":{"type":"string"},"likes":{"type":"number"},"replies":{"type":"number","description":"Reply COUNT, not the nested replies array. `data` carries the array under the same name."},"reposts":{"type":"number"},"platform_specific":{}},"required":["id","author","text","likes","replies","reposts"],"description":"Deprecated Bluesky-only shape. Use `data` instead."},"LegacyVimeoComment":{"type":"object","properties":{"uri":{"type":"string"},"text":{"type":"string"},"createdOn":{"type":"string"},"user":{"type":"object","properties":{"name":{"type":"string"},"uri":{"type":"string"}},"required":["name","uri"]}},"required":["uri","text","createdOn","user"],"description":"Deprecated Vimeo-only shape (raw Vimeo comment). Use `data` instead."},"NormalizedReply":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the reply","example":"x_123456789_1704067200000"},"author":{"type":"string","description":"Author of the comment as the network identifies them (username, display name, or platform URN). Unchanged by `resolve_author_names`.","example":"johndoe"},"author_name":{"type":["string","null"],"description":"Human-readable author name. `null` when the network does not expose one, or on LinkedIn when `resolve_author_names` was not set or the commenter could not be resolved - `author` then stays the raw URN.","example":"John Doe"},"author_username":{"type":"string","description":"The author's handle or vanity name, when the network provides one","example":"johndoe"},"author_avatar_url":{"type":"string","description":"URL of the author's profile picture, when the network provides one. May expire.","example":"https://media.licdn.com/dms/image/example.jpg"},"author_profile_url":{"type":"string","description":"Public profile URL of the author, when it can be derived","example":"https://www.linkedin.com/in/johndoe"},"text":{"type":"string","description":"Reply content","example":"Great post!"},"created_at":{"type":["string","null"],"description":"ISO timestamp when the reply was created","example":"2025-01-15T10:30:00Z"},"like_count":{"type":"number","description":"Number of likes on the comment, when the platform reports it","example":5},"platform_specific":{"description":"Platform-specific data","example":{}},"replies":{"type":"array","items":{"$ref":"#/components/schemas/NormalizedReply"},"description":"Replies nested under this comment. Present only when include_replies=true. Facebook, Instagram, and LinkedIn cap threading at this single tier, so their replies never carry a `replies` key of their own; Threads nests to any depth, so a reply there can carry further replies recursively."}},"required":["id","author","author_name","text","created_at"]}}},"paths":{"/v1/posts":{"post":{"operationId":"postV1Posts","summary":"Create a post","description":"Create a new post to publish or schedule across multiple social media platforms simultaneously. A post might mean something different depending on the context and the platform - for example, a single X post, a multi-thread X post, a LinkedIn post, or an Instagram Reel or Story. Posts use a container model where each container holds content for one segment (e.g., individual tweets in an X thread, or carousel items). Specify target platforms via the `accounts` array, referencing each connected account by its `id` from `GET /v1/social-accounts` (preferred) or its exact username - network names (e.g. `x`, `linkedin`) and nicknames are not identifiers and do not resolve. The request is rejected with 400 only when none of the supplied identifiers resolve, so an entry matching nothing alongside one that matches is dropped silently. Provide the post body either as a single `content` string or as a `containers` array of `{ content, media }` objects - one of the two is required. Attach media by adding `media` objects to a container, each `{ url, filename }` where `url` is the publicly accessible URL returned by the confirm-upload step (see Media API); media is not referenced by id. Include `scheduledAt` (ISO 8601 format, e.g. `2025-06-01T12:00:00Z`) to schedule the post for future publishing, or omit it to publish immediately. Platform-specific options (e.g., TikTok privacy settings, YouTube categories, Pinterest board IDs) are provided as top-level keys named after the network (`tiktok`, `youtube`, `instagram`, `threads`, `google_business`, `pinterest`, `linkedin`, `facebook`, `vimeo`, `reddit`, `x`, `bluesky`) - not nested under any wrapper object. Every one of those blocks also accepts `content`: text published on that network instead of the shared body, so one post can carry a professional LinkedIn version, a casual Instagram caption and a short X version while the top-level `content` remains the fallback for the rest. Only the root text is overridden - media, first comments and thread replies stay shared, and two accounts on the same network get the same override. The response echoes these back nested under `networkOverrideConfiguration` (e.g. `tiktokConfiguration`), so the request and response shapes for platform config differ by design. Unrecognised top-level keys are ignored and reported in the response `warnings` array. Threads limits each container to 500 characters, so a request targeting a Threads account - on its own or cross-posted alongside other networks - is rejected with 400 if any container is longer; when `threads.content` is set, that override is checked in place of the shared body, which is how a long LinkedIn post cross-posts to Threads. Managed-key YouTube uploads are admitted at execution time from a shared paced daily pool; scheduling never reserves capacity. A denied YouTube destination fails independently and its existing error string (also in webhook results) contains a stable reason code (release_pending, org_limit_reached, daily_limit_reached, provider_quota_exhausted, or quota_unavailable) and an ISO 8601 retryAt when known. retryAt estimates earliest eligibility for a capacity release, organization-cap relaxation, or next Pacific quota day; it is not a reservation. Retry manually. If another destination already published, create a new YouTube-only post targeting the failed account because partially published posts cannot be edited or rescheduled. BYOK bypasses the shared pool; dispatched attempts are not refunded on failure. Google Business EVENT and OFFER posts require an event title, startDate, startTime, endDate and endTime. CALL buttons omit callToAction.url; other action types require it. Google ignores callToAction on OFFER posts. Facebook videoCoverUrl accepts a public HTTP(S) JPEG cover up to 10,000,000 bytes for exactly one video, including Reels. Stories cannot have covers. The image is validated before video upload; applying the cover after publication is best-effort and rejection leaves the video published.","tags":["Posts"],"parameters":[{"in":"header","name":"Idempotency-Key","required":false,"description":"Optional client-generated key (a UUID v4 is ideal, max 255 printable ASCII characters) that makes this create safe to retry. Retrying with the same key and the same request body returns the original response together with an `Idempotency-Replayed: true` header, instead of creating a second post. Keys are scoped to your organization and to this endpoint, and are remembered for 24 hours.","schema":{"type":"string","maxLength":255,"example":"b1f6c3f4-8f0e-4a5b-9c2d-7e1a0b3c4d5e"}}],"responses":{"200":{"description":"Post created successfully and scheduled for publishing. Carries `Idempotency-Replayed: true` when this is a replay of an earlier request with the same `Idempotency-Key`.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the post creation was successful","example":true},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal warnings about the request. Used to report keys in the request body that were not recognised and were therefore ignored - both top-level keys (e.g. the deprecated 'socialAccountIds'/'mediaIds' shapes, or the response-only 'networkOverrideConfiguration' wrapper) and keys inside a platform-config block (e.g. 'tiktok': { 'post_mode': ... } when the field is named 'postMode'). Unrecognised keys are dropped before the post is stored, so a warning here means the setting you sent is not in effect. Present only when there is at least one warning.","example":["Ignored unrecognised key 'post_mode' in 'tiktok'. Did you mean 'postMode'?"]},"post":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the created post (sqids encoded)","example":"9dyJS"},"orgId":{"type":"string","description":"Organization ID that owns this post","example":"abc123"},"publishedAt":{"type":["string","null"],"format":"date-time","description":"When the post was published, null if not yet published","example":null},"scheduledAt":{"type":["string","null"],"format":"date-time","description":"When the post is scheduled to be published. If empty, the post is immediately published.","example":"2025-09-20T14:00:00Z"},"isDraft":{"type":"boolean","description":"Whether this is a draft post","example":false},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the post was created","example":"2025-01-15T10:30:00Z"},"socialAccounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The account's encoded id - the same value GET /v1/social-accounts returns and the same value you put in the request's `accounts` array. This array lists exactly the accounts that resolved, so comparing these ids against the ids you sent is how you detect entries that were silently ignored. Note this is targeting, not publishing: per-account publish status arrives on GET /v1/posts/:id, not here.","example":"Kx7vQ"},"nickname":{"type":"string","description":"User-defined nickname for this social account","example":"My Company"},"network":{"type":"string","description":"Social network identifier (e.g., 'facebook', 'x', 'instagram', 'linkedin', 'threads', 'youtube', 'tiktok')","example":"x"},"username":{"type":"string","description":"Username or handle on the social network","example":"mycompany"}},"required":["id","nickname","network","username"],"description":"Social account associated with the post"},"description":"Array of social accounts where this post will be published","example":[]},"containers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the container (sqids encoded)","example":"8xKmL"},"content":{"type":"string","description":"The text content of this post container","example":"Check out our new product launch! #excited"},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier for the media file","example":123},"url":{"type":"string","format":"uri","description":"Publicly accessible HTTPS URL to the media file","example":"https://media.yoursite.com/images/launch.jpg"},"filename":{"type":"string","minLength":1,"description":"Clean filename with extension","example":"launch-photo.jpg"}},"required":["id","url","filename"],"description":"Media attachment in the response"},"description":"Array of media attachments for this container","example":[]}},"required":["id","content","media"],"description":"A post container in the response"},"description":"Array of post containers with content and media","example":[]},"networkOverrideConfiguration":{"type":["object","null"],"additionalProperties":{},"description":"The stored platform-specific configuration, keyed by network (e.g. 'tiktokConfiguration', 'youtubeConfiguration'). Echoed back from what was persisted - note the request accepts these as top-level keys named after the network ('tiktok', 'youtube', ...), so the request and response shapes differ. Null when no platform config was provided.","example":null},"scheduled":{"type":"boolean","description":"Whether a publishing job is actually queued for this post. This is the field to check to confirm a post will publish - `true` means the job exists, `false` means nothing will publish it until it is rescheduled (PATCH a `scheduledAt`). Drafts are never queued, so they report `false` by design. Prefer this over inspecting internal fields.","example":true}},"required":["id","orgId","publishedAt","scheduledAt","isDraft","createdAt","socialAccounts","containers","scheduled"],"description":"The post object","example":{"id":"9dyJS","orgId":"abc123","publishedAt":null,"scheduledAt":"2025-09-20T14:00:00Z","isDraft":false,"createdAt":"2025-01-15T10:30:00Z","scheduled":true,"socialAccounts":[{"id":"Kx7vQ","nickname":"My Company","network":"x","username":"mycompany"}],"containers":[{"id":"8xKmL","content":"Check out our new product launch! #excited","media":[{"id":123,"url":"https://media.yoursite.com/images/launch.jpg","filename":"launch-photo.jpg"}]}]}}},"required":["success","post"],"description":"Successful post creation response"}}}},"202":{"description":"The post was created but could not be queued for publishing (a transient Cloud Tasks failure). The post is returned with `scheduled: false` and nothing will publish it until you requeue it by PATCHing `scheduledAt`. Not returned for an out-of-window `scheduledAt` - that is rejected with 400 before the post is created.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the post creation was successful","example":true},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal warnings about the request. Used to report keys in the request body that were not recognised and were therefore ignored - both top-level keys (e.g. the deprecated 'socialAccountIds'/'mediaIds' shapes, or the response-only 'networkOverrideConfiguration' wrapper) and keys inside a platform-config block (e.g. 'tiktok': { 'post_mode': ... } when the field is named 'postMode'). Unrecognised keys are dropped before the post is stored, so a warning here means the setting you sent is not in effect. Present only when there is at least one warning.","example":["Ignored unrecognised key 'post_mode' in 'tiktok'. Did you mean 'postMode'?"]},"post":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the created post (sqids encoded)","example":"9dyJS"},"orgId":{"type":"string","description":"Organization ID that owns this post","example":"abc123"},"publishedAt":{"type":["string","null"],"format":"date-time","description":"When the post was published, null if not yet published","example":null},"scheduledAt":{"type":["string","null"],"format":"date-time","description":"When the post is scheduled to be published. If empty, the post is immediately published.","example":"2025-09-20T14:00:00Z"},"isDraft":{"type":"boolean","description":"Whether this is a draft post","example":false},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the post was created","example":"2025-01-15T10:30:00Z"},"socialAccounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The account's encoded id - the same value GET /v1/social-accounts returns and the same value you put in the request's `accounts` array. This array lists exactly the accounts that resolved, so comparing these ids against the ids you sent is how you detect entries that were silently ignored. Note this is targeting, not publishing: per-account publish status arrives on GET /v1/posts/:id, not here.","example":"Kx7vQ"},"nickname":{"type":"string","description":"User-defined nickname for this social account","example":"My Company"},"network":{"type":"string","description":"Social network identifier (e.g., 'facebook', 'x', 'instagram', 'linkedin', 'threads', 'youtube', 'tiktok')","example":"x"},"username":{"type":"string","description":"Username or handle on the social network","example":"mycompany"}},"required":["id","nickname","network","username"],"description":"Social account associated with the post"},"description":"Array of social accounts where this post will be published","example":[]},"containers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the container (sqids encoded)","example":"8xKmL"},"content":{"type":"string","description":"The text content of this post container","example":"Check out our new product launch! #excited"},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier for the media file","example":123},"url":{"type":"string","format":"uri","description":"Publicly accessible HTTPS URL to the media file","example":"https://media.yoursite.com/images/launch.jpg"},"filename":{"type":"string","minLength":1,"description":"Clean filename with extension","example":"launch-photo.jpg"}},"required":["id","url","filename"],"description":"Media attachment in the response"},"description":"Array of media attachments for this container","example":[]}},"required":["id","content","media"],"description":"A post container in the response"},"description":"Array of post containers with content and media","example":[]},"networkOverrideConfiguration":{"type":["object","null"],"additionalProperties":{},"description":"The stored platform-specific configuration, keyed by network (e.g. 'tiktokConfiguration', 'youtubeConfiguration'). Echoed back from what was persisted - note the request accepts these as top-level keys named after the network ('tiktok', 'youtube', ...), so the request and response shapes differ. Null when no platform config was provided.","example":null},"scheduled":{"type":"boolean","description":"Whether a publishing job is actually queued for this post. This is the field to check to confirm a post will publish - `true` means the job exists, `false` means nothing will publish it until it is rescheduled (PATCH a `scheduledAt`). Drafts are never queued, so they report `false` by design. Prefer this over inspecting internal fields.","example":true}},"required":["id","orgId","publishedAt","scheduledAt","isDraft","createdAt","socialAccounts","containers","scheduled"],"description":"The post object","example":{"id":"9dyJS","orgId":"abc123","publishedAt":null,"scheduledAt":"2025-09-20T14:00:00Z","isDraft":false,"createdAt":"2025-01-15T10:30:00Z","scheduled":true,"socialAccounts":[{"id":"Kx7vQ","nickname":"My Company","network":"x","username":"mycompany"}],"containers":[{"id":"8xKmL","content":"Check out our new product launch! #excited","media":[{"id":123,"url":"https://media.yoursite.com/images/launch.jpg","filename":"launch-photo.jpg"}]}]}}},"required":["success","post"],"description":"Successful post creation response"}}}},"400":{"description":"Invalid request payload or validation error - including a container that exceeds a targeted network's character limit (Threads: 500) - or a malformed `Idempotency-Key`","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"409":{"description":"Another request with the same `Idempotency-Key` is still in progress. Retry after the interval given in the `Retry-After` header.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"422":{"description":"The supplied `Idempotency-Key` was already used with a different request body. Use a new key.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"containers":{"type":"array","items":{"type":"object","properties":{"content":{"type":"string","default":"","description":"The text content of this post container. Optional when the container carries media - omit it for a media-only post, and for Facebook/Instagram Stories, which are published without a caption. Can include platform-specific formatting (e.g., hashtags, mentions) but it's not guaranteed to be rendered correctly by all platforms. Threads rejects containers longer than 500 characters, so a post targeting a Threads account (including when cross-posting alongside other networks) is rejected with 400 if any container exceeds it.","example":"Check out our new product launch! #excited"},"media":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Publicly accessible HTTPS URL to the media file. Requirements: (1) Must be publicly accessible - Outstand downloads media server-side, (2) Must use HTTPS, (3) Must return valid Content-Type header, (4) Must be available at publish time - media is fetched when publishing, not when creating the post, (5) Must not require authentication. Supported formats vary by platform: Facebook/X/LinkedIn support JPEG, PNG, GIF; Instagram/Threads support JPEG, PNG; Videos (MP4, MOV) supported on most platforms.","example":"https://media.yoursite.com/images/launch.jpg"},"filename":{"type":"string","minLength":1,"description":"Clean filename with extension (e.g., 'photo.jpg', 'video.mp4'). Do not include query parameters or URL fragments. The extension helps determine media type for platform-specific processing.","example":"launch-photo.jpg"},"altText":{"type":"string","description":"Per-image alternative text for accessibility. Currently applied on Bluesky, which supports alt text per image (app.bsky.embed.images). Other platforms use their own network-specific alt-text configuration and ignore this field.","example":"A product photo showing the new dashboard on a laptop screen"}},"required":["url","filename"],"description":"Media attachment for a post container. Outstand handles media processing per platform: Facebook media is downloaded and re-uploaded as binary data; Instagram/Threads URLs are passed to their container APIs; X media is downloaded and uploaded via their media API; LinkedIn URLs are passed directly; YouTube/TikTok videos are downloaded and uploaded via their respective upload APIs."},"default":[],"description":"Array of media attachments for this container. Multiple media files can be attached per container.","example":[]}},"description":"A single content container within a post. Posts can have multiple containers. A container can have multiple media attachments. Each container that is not the root container, will be published as a reply to the root container."},"description":"Array of post containers. Each container represents a combo for the post content with its own text and media. Use this if you want to create a post with an automated reply to the root container. Otherwise, use the 'content' field.","example":[{"content":"Our newest feature is live! Check it out 🚀","media":[{"url":"https://example.com/feature.png","filename":"feature.png"}]}]},"content":{"type":"string","description":"Simple text content for the post. Use this for single-container posts. Either 'content' or 'containers' must be provided. If you use 'containers', the first container will be the root container and the rest will be published as replies to the root container. Threads rejects content longer than 500 characters, so a post targeting a Threads account (including when cross-posting alongside other networks) is rejected with 400 if it exceeds that - unless `threads.content` supplies a Threads-only body, which is what gets checked instead. To publish different text on one network, put `content` inside that network's block (e.g. `linkedin.content`); this field is then the fallback for every network without one.","example":"Excited to announce our latest update!"},"accounts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Array of social media account identifiers where this post will be published. Each entry is either the account's `id` as returned by GET /v1/social-accounts (preferred) or the account's exact username. Network names (e.g. 'x', 'linkedin') and nicknames are NOT identifiers and will not resolve. Account ids are opaque - pass back exactly what the API returned and never construct one. A username is matched by exact equality with no network or active-account filter, so a username shared across networks publishes to every account that carries it. The request is rejected with 400 only when NONE of the supplied identifiers resolve; if at least one resolves, the unresolved entries are silently ignored, so compare the returned `socialAccounts` against what you sent.","example":["Kx7vQ","Tm2bN"]},"scheduledAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp for when the post should be automatically published. If not provided, the post will be published immediately (unless it's a draft). If a time in the past is provided, the post will be published immediately. The maximum time in the future is 30d - a later timestamp is rejected with 400 and no post is created.","example":"2026-02-20T14:00:00Z"},"threads":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Threads instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Threads accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. The 500-character Threads limit is checked against this override instead of the shared body, so a longer shared body can still cross-post to Threads.","example":"A more conversational take for Threads"},"countries":{"type":"array","items":{"type":"string"},"description":"A list of valid ISO 3166-1 alpha-2 country codes that represents the countries where this media should be shown. If this parameter is passed in, the post will not be shown to Threads profiles in countries outside of this list.","example":["US","DK"]},"reply_control":{"type":"string","enum":["everyone","accounts_you_follow","mentioned_only"],"description":"Controls who can reply to this Threads post. Set at publish time only - it cannot be changed after the post goes live. Defaults to 'everyone'.","example":"accounts_you_follow"}},"description":"Custom and override configuration for Threads content. Not to be confused with threaded content or anything other than the Threads By Instagram - the social network."},"instagram":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Instagram instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Instagram accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Takes precedence over the older 'caption' field.","example":"Casual Instagram caption"},"publishAsStory":{"type":"boolean","description":"When true, publish as an Instagram Story instead of a feed post. Stories support a single image or video (no carousel), no caption, and expire after 24 hours. Recommended aspect ratio: 9:16.","example":true},"locationId":{"type":"string","description":"The ID of the location to publish the post to.","example":"1234567890"},"reelThumbOffset":{"type":"number","description":"The offset in milliseconds for the thumbnail of the Reel. Not setting this will use the default thumbnail offset for the Reel, which is 500msec. Ignored if reelCoverUrl is set.","example":1000},"reelCoverUrl":{"type":"string","format":"uri","description":"Public URL of a JPEG image to use as the cover image of the Reel. Reels only - not supported for images, carousels or Stories. Instagram fetches the image server-side, so it must be publicly reachable. Max 8MB, sRGB, 9:16 aspect ratio recommended. Takes precedence over reelThumbOffset.","example":"https://cdn.example.com/reel-cover.jpg"},"userTags":{"type":"array","items":{"type":"object","properties":{"username":{"type":"string"},"x":{"type":"number"},"y":{"type":"number"}},"required":["username","x","y"]},"description":"Array of user tags to add to the post","example":[{"username":"@instagram","x":0.5,"y":0.5}]},"altText":{"type":"string","description":"The alt text for the post - overrides the alt text in the post content/first container.","example":"A picture of our latest product"},"caption":{"type":"string","description":"The caption for the post - overrides the caption in the post content/first container.","example":"Check out our new product! #excited"},"collaborators":{"type":"array","items":{"type":"string"},"maxItems":3,"description":"Array of up to 3 public Instagram usernames to invite as collaborators. Collaborators receive an invite; once accepted, the post appears on both profiles. Only supported for feed posts (IMAGE) and Reels, not Stories.","example":["partner_account","brand_account"]},"isAiGenerated":{"type":"boolean","description":"Self-disclosure that the media is AI-generated, surfaced as Instagram's \"AI info\" label. Applies to feed posts, Reels, carousels and Stories. For carousels the flag is set on the carousel itself, not the individual items.","example":true},"trialReel":{"type":"object","properties":{"graduationStrategy":{"type":"string","enum":["MANUAL","SS_PERFORMANCE"],"description":"MANUAL: the creator graduates the trial reel to followers manually in the Instagram app. SS_PERFORMANCE: Meta automatically graduates the trial reel if it performs well with non-followers.","example":"MANUAL"}},"required":["graduationStrategy"],"description":"Publish this Reel as a Trial Reel, shared only to non-followers until graduated. Requires a single video published as a Reel - not supported for Stories, carousels, or images.","example":{"graduationStrategy":"MANUAL"}}},"description":"Custom and override configuration for Instagram content.","example":{"locationId":"1234567890","reelThumbOffset":3240}},"youtube":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on YouTube instead of the shared post body. Replaces the top-level 'content' (the first container's text) for YouTube accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Becomes the video description; 'title' still applies.","example":"A longer description for the YouTube video."},"isShort":{"type":"boolean","description":"Whether the video is a YouTube Short.","example":true},"categoryId":{"type":"string","description":"The ID of the category to publish the post to.","example":"1234567890"},"privacyStatus":{"type":"string","enum":["public","private","unlisted"],"description":"The privacy status of the post.","example":"public"},"madeForKids":{"type":"boolean","description":"Whether the video is made for kids.","example":true},"containsSyntheticMedia":{"type":"boolean","description":"Self-disclosure that the video contains realistic altered or synthetic content, for example AI-generated video. Maps to YouTube's status.containsSyntheticMedia and surfaces as an altered-content label on the video. Omitted from the upload entirely when not set - Outstand never declares this on your behalf. Cannot be changed through Outstand once the video is published.","example":true},"tags":{"type":"array","items":{"type":"string"},"description":"The tags for the post.","example":["tag1","tag2"]},"title":{"type":"string","description":"The title of the post.","example":"My new video"},"thumbnailUrl":{"type":"string","format":"uri","description":"Public URL of a JPEG or PNG image to set as the video's custom thumbnail. Max 50MB; 16:9 recommended for videos (3840x2160, at least 640px wide) and 9:16 for Shorts. The image is downloaded and checked before the video is uploaded, so an unreachable, oversized or non-JPEG/PNG image fails the publish. Setting it on YouTube is best-effort: if YouTube rejects it - for example because the channel is not verified for custom thumbnails - the video is still published with an auto-generated thumbnail. YouTube may ignore custom thumbnails on Shorts.","example":"https://cdn.example.com/video-thumbnail.jpg"}},"description":"Custom and override configuration for YouTube content."},"tiktok":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on TikTok instead of the shared post body. Replaces the top-level 'content' (the first container's text) for TikTok accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. For photo posts, 'title' and 'description' still take precedence when set.","example":"Hook-first caption for TikTok"},"postMode":{"type":"string","enum":["DIRECT_POST","MEDIA_UPLOAD"],"description":"The posting mode for TikTok. Defaults to MEDIA_UPLOAD when omitted. MEDIA_UPLOAD delivers the media to the creator's TikTok inbox as a draft - they finish the caption, visibility and settings in the TikTok app and publish it themselves - and is not subject to TikTok's 24-hour cap on distinct publishing creators per app. DIRECT_POST publishes straight to the profile, requires privacyLevel, and does count against that cap (reached_active_user_cap).","example":"MEDIA_UPLOAD"},"privacyLevel":{"type":"string","enum":["PUBLIC_TO_EVERYONE","MUTUAL_FOLLOW_FRIENDS","FOLLOWER_OF_CREATOR","SELF_ONLY"],"description":"The privacy level for the TikTok post. Required when postMode is DIRECT_POST - must come from the creator_info privacy_level_options. Ignored for MEDIA_UPLOAD, where the creator picks visibility in the TikTok app.","example":"PUBLIC_TO_EVERYONE"},"title":{"type":"string","maxLength":90,"description":"Photo post title (the bold header), mapped to TikTok's post_info.title. Max 90 UTF-16 runes. Photo posts only. When omitted, the post content is used and truncated to 90 characters.","example":"Summer collection drop"},"description":{"type":"string","maxLength":4000,"description":"Photo post description (the caption), mapped to TikTok's post_info.description. Max 4000 UTF-16 runes. Photo posts only. When omitted, the post content is used.","example":"Our new summer collection is here. Swipe through to see every look."},"disableComment":{"type":"boolean","description":"Whether to disable comments on the TikTok post. Applies to both video and photo posts.","example":false},"disableDuet":{"type":"boolean","description":"Whether to disable duets on the TikTok post. Video posts only - ignored for photo posts (TikTok does not support Duet on photos).","example":false},"disableStitch":{"type":"boolean","description":"Whether to disable stitches on the TikTok post. Video posts only - ignored for photo posts (TikTok does not support Stitch on photos).","example":false},"videoCoverTimestampMs":{"type":"integer","minimum":0,"description":"Timestamp in milliseconds of the video frame to use as the cover, mapped to TikTok's post_info.video_cover_timestamp_ms. DIRECT_POST video posts only - ignored for MEDIA_UPLOAD (the creator picks the cover in the TikTok app) and for photo posts. When omitted, TikTok picks the default cover.","example":3500},"autoAddMusic":{"type":"boolean","description":"Whether TikTok should automatically add recommended music to the post. Applies to photo posts only (ignored for video posts). Defaults to false when not set.","example":false},"brandContentToggle":{"type":"boolean","description":"Branded content disclosure - marks the post as a paid partnership (TikTok UX Guideline Point 3).","example":false},"brandOrganicToggle":{"type":"boolean","description":"Your brand disclosure - marks the post as promotional content for your own brand (TikTok UX Guideline Point 3).","example":false},"isAigc":{"type":"boolean","description":"Whether the content is AI-generated.","example":false}},"description":"Custom and override configuration for TikTok content."},"google_business":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Google Business instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Google Business accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks.","example":"Local-audience wording for Google Business."},"topicType":{"type":"string","enum":["STANDARD","EVENT","OFFER"],"description":"The type of Google Business post. STANDARD is a regular post, EVENT includes event details, OFFER includes promotional offer details.","example":"STANDARD"},"callToAction":{"type":"object","properties":{"actionType":{"type":"string","enum":["BOOK","ORDER","SHOP","LEARN_MORE","SIGN_UP","CALL"],"description":"The type of call-to-action button.","example":"LEARN_MORE"},"url":{"type":"string","format":"uri","description":"Required for non-CALL actions. Omit for CALL; any supplied URL is ignored when publishing.","example":"https://example.com"}},"required":["actionType"],"description":"Call-to-action button configuration. Google ignores this for OFFER posts."},"event":{"type":"object","properties":{"title":{"type":"string","description":"The title of the event.","example":"Grand Opening"},"startDate":{"type":"object","properties":{"year":{"type":"number"},"month":{"type":"number"},"day":{"type":"number"}},"required":["year","month","day"],"description":"The start date of the event.","example":{"year":2026,"month":3,"day":15}},"startTime":{"type":"object","properties":{"hours":{"type":"number"},"minutes":{"type":"number"}},"required":["hours","minutes"],"description":"The start time of the event.","example":{"hours":9,"minutes":0}},"endDate":{"type":"object","properties":{"year":{"type":"number"},"month":{"type":"number"},"day":{"type":"number"}},"required":["year","month","day"],"description":"The end date of the event.","example":{"year":2026,"month":3,"day":15}},"endTime":{"type":"object","properties":{"hours":{"type":"number"},"minutes":{"type":"number"}},"required":["hours","minutes"],"description":"The end time of the event.","example":{"hours":17,"minutes":0}}},"required":["title","startDate","startTime","endDate","endTime"],"description":"Event details with all four schedule fields. Required when topicType is EVENT or OFFER."},"offer":{"type":"object","properties":{"couponCode":{"type":"string","description":"The coupon code for the offer.","example":"SAVE20"},"redeemOnlineUrl":{"type":"string","format":"uri","description":"URL where the offer can be redeemed online.","example":"https://example.com/redeem"},"termsConditions":{"type":"string","description":"Terms and conditions for the offer.","example":"Valid until March 31, 2026."}},"description":"Offer details. Used when topicType is OFFER."}},"description":"Custom and override configuration for Google Business Profile content."},"pinterest":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Pinterest instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Pinterest accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Becomes the pin description; 'title' still applies.","example":"Pin description written for Pinterest search."},"board_id":{"type":"string","minLength":1,"description":"The ID of the Pinterest board to pin to. Required for Pinterest.","example":"123456789"},"cover_image_url":{"type":"string","format":"uri","description":"Cover image URL for video pins. Pinterest requires a cover image for videos; if omitted, the first frame of the video is used automatically.","example":"https://example.com/cover.jpg"},"link":{"type":"string","format":"uri","description":"Destination URL when the pin is clicked.","example":"https://example.com/product"},"alt_text":{"type":"string","maxLength":500,"description":"Alt text for the pin image (max 500 chars).","example":"A red dress on a model"},"title":{"type":"string","maxLength":100,"description":"Pin title (max 100 chars). Defaults to first 100 chars of content.","example":"New Summer Collection"}},"required":["board_id"],"description":"Custom configuration for Pinterest content. board_id is required when publishing to Pinterest."},"vimeo":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Vimeo instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Vimeo accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Becomes the video description; 'title' and 'description' still take precedence when set.","example":"Description for the Vimeo upload."},"title":{"type":"string"},"description":{"type":"string"},"privacyView":{"type":"string","enum":["anybody","unlisted","nobody","password","contacts","users","disable"]},"privacyEmbed":{"type":"string","enum":["public","private","whitelist"]}},"description":"Vimeo-specific video configuration"},"linkedin":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on LinkedIn instead of the shared post body. Replaces the top-level 'content' (the first container's text) for LinkedIn accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Mentions are resolved against this text.","example":"A more professional, detailed version for LinkedIn."},"article":{"type":"object","properties":{"source":{"type":"string","format":"uri","description":"The URL the article card links to. Required. LinkedIn uses this as the primary link for the card.","example":"https://example.com/blog/my-article"},"title":{"type":"string","minLength":1,"maxLength":400,"description":"Title shown on the article card (max 400 chars). Overrides LinkedIn's OG title scraping.","example":"How We Scaled to 1M Users"},"description":{"type":"string","maxLength":4000,"description":"Short description shown beneath the title on the card (max 4000 chars).","example":"A deep dive into our infrastructure journey."},"thumbnailUrl":{"type":"string","format":"uri","description":"Public image URL to use as the card thumbnail. We upload this to LinkedIn's Images API and use the resulting URN. LinkedIn does not accept raw URLs as thumbnails.","example":"https://example.com/og-image.png"}},"required":["source"],"description":"Publish the post as a LinkedIn article link card instead of a plain feed post. The container content becomes the commentary above the card."},"mentions":{"type":"array","items":{"type":"object","properties":{"urn":{"type":"string","pattern":"^urn:li:(organization|person):.+","description":"The URN of the LinkedIn entity to mention. Organizations use 'urn:li:organization:<id>'; members use 'urn:li:person:<id>'.","example":"urn:li:organization:1337"},"text":{"type":"string","minLength":1,"description":"The entity's exact, case-sensitive registered name as it appears on LinkedIn (e.g. 'Outstand.so', not 'Outstand'). This string must also appear verbatim in the post content - the first occurrence is converted into a clickable mention. If the text does not match the entity's full registered name, the API returns an error rather than publishing a silently-broken tag.","example":"LinkedIn"}},"required":["urn","text"]},"description":"Tag LinkedIn members or organizations by mentioning them inline. For each mention, the matching text in the post content is rewritten into LinkedIn's annotation format. Organization text must be the entity's exact, case-sensitive full name - pass the wrong name and the API will reject the request before publishing.","example":[{"urn":"urn:li:organization:1337","text":"LinkedIn"}]},"caption":{"type":"string","minLength":1,"maxLength":400,"description":"Caption shown directly beneath the media in the LinkedIn feed. When omitted no caption is shown - LinkedIn does not add one of its own. Only visible on single-image and single-video posts; multi-image posts accept it but LinkedIn does not render a per-image caption. Ignored for PDF/document posts, which always carry a title, and for article-card posts, which have no media.","example":"Behind the scenes at our Berlin office"}},"description":"Custom configuration for LinkedIn content."},"facebook":{"type":"object","properties":{"videoCoverUrl":{"type":"string","format":"uri","pattern":"^https?:\\/\\/","description":"Public JPEG cover URL, maximum 10,000,000 bytes. Requires one video; unavailable for Stories. Match the video aspect ratio. Validated before video upload; applying the cover after publication is best-effort and failure leaves the video published."},"content":{"type":"string","minLength":1,"description":"Text to publish on Facebook instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Facebook accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Stories reject any caption, so it cannot be combined with publishAsStory.","example":"Facebook-specific wording for the Page post."},"mentions":{"type":"array","items":{"type":"object","properties":{"pageId":{"type":"string","minLength":1,"description":"The numeric Facebook Page ID to tag (e.g. '107934268209008'). The API verifies that this page exists and is accessible before publishing - an invalid or inaccessible ID will cause the post to fail with a clear error.","example":"123456789"},"text":{"type":"string","minLength":1,"description":"The anchor text that marks where the @[pageId] tag is inserted. This string must appear verbatim in the content of a comment container (not the post body) - if it is missing from all comments, the API rejects the request rather than silently skipping the tag. Note: Meta may still not render the tag as a clickable link for pages you do not manage or if the app's 'Page Mentioning' feature has not been approved (a platform limitation that cannot be detected before publishing).","example":"Outstand"}},"required":["pageId","text"]},"description":"Tag other Facebook Pages by mentioning them inline in a comment. IMPORTANT: Meta's Graph API only supports @[pageId] page mentions in comments, not in the main post body. Mentions are applied to comment containers (containers[1+]) - you must include at least one comment container whose text contains the anchor. Only Pages can be tagged; tagging personal profiles is not supported. Meta may silently suppress tags for pages your account has no relationship with.","example":[{"pageId":"123456789","text":"Outstand"}]},"publishAsStory":{"type":"boolean","description":"When true, publish to the Page's Story instead of the feed. Stories take exactly one image or video (no carousel), no caption, no comments and no mentions - the request is rejected rather than silently dropping them, so a Story cannot be cross-posted in the same call as captioned content for other networks. Stories expire after 24 hours, after which analytics return null. Recommended aspect ratio: 9:16 (1080x1920).","example":true},"publishAsReel":{"type":"boolean","description":"When true, publish the video as a Page Reel instead of a plain Page video. Requires exactly one video; the post content is used as the Reel description and comment containers are published as comments. Recommended aspect ratio: 9:16 (1080x1920), 3-90 seconds. Cannot be combined with publishAsStory.","example":true}},"description":"Custom configuration for Facebook content."},"reddit":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Reddit instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Reddit accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Ignored for link posts (when 'url' is set).","example":"Markdown body written for the subreddit."},"subreddit":{"type":"string","minLength":1,"description":"The subreddit to post to, without the 'r/' prefix (e.g. 'programming'). Required for Reddit. Use 'u_<username>' to post to a user profile.","example":"programming"},"title":{"type":"string","minLength":1,"maxLength":300,"description":"The post title (max 300 chars). Required for Reddit - every Reddit post has a title separate from its body.","example":"We built a unified social media API"},"url":{"type":"string","format":"uri","description":"When set, the post is published as a link post pointing at this URL and the container content is ignored (Reddit does not allow a body on link posts). When omitted, the post is a text (self) post with the container content as its markdown body.","example":"https://example.com/blog/launch"},"flairId":{"type":"string","description":"Flair template ID to apply to the post. Some subreddits require flair and auto-remove posts without it.","example":"c4acd8f2-9e2f-11eb-8000-0e3e6a4e8e8a"},"flairText":{"type":"string","maxLength":64,"description":"Custom flair text (max 64 chars), used together with flairId when the flair template allows editable text.","example":"Discussion"},"nsfw":{"type":"boolean","description":"Mark the post as NSFW. Defaults to false.","example":false},"spoiler":{"type":"boolean","description":"Mark the post as a spoiler. Defaults to false.","example":false},"sendReplies":{"type":"boolean","description":"Whether the author receives inbox notifications for replies. Defaults to true.","example":true}},"required":["subreddit","title"],"description":"Custom configuration for Reddit content. subreddit and title are required when publishing to Reddit. Only text (self) and link posts are supported - media containers are rejected."},"x":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on X instead of the shared post body. Replaces the top-level 'content' (the first container's text) for X accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. X's own length limit is not checked at request time.","example":"Short, punchy version for X"}},"description":"Custom configuration for X content. Currently only the per-network content override."},"bluesky":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Bluesky instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Bluesky accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Bluesky's 300-character limit is not checked at request time.","example":"A version for Bluesky, within 300 characters"}},"description":"Custom configuration for Bluesky content. Currently only the per-network content override."},"processMedia":{"type":"boolean","description":"Opt in to automatic video processing. When true, video media is inspected and re-encoded to each target network's requirements (container, codec, resolution, frame rate and file size) before publishing, and the correct rendition is sent to each network. Leave unset to publish the file exactly as supplied. Billed per rendition produced, by output duration in 10-second blocks (rounded up, minimum one block per rendition); validation and inspection are always free. Has no effect on posts without video media.","example":true}},"required":["accounts"],"description":"Schema for creating a new Post. A 'post' might mean something different depending on the context. For example, a 'post' might be a single tweet, a single LinkedIn post, a single Instagram Reel or Story, etc."}}}}},"get":{"operationId":"getV1Posts","summary":"List posts","description":"Get a paginated list of posts for the current organization with optional filters. Results include post content, containers, scheduling status, and publishing state. Each returned post includes its unique ID, which can be used with GET /v1/posts/:id for full details or DELETE /v1/posts/:id to cancel a scheduled post.\n\n**Filtering:**\n- `tenant_id` - Filter by tenant ID for multi-tenant applications (exact match). Posts carry no tenant of their own, so a post matches when at least one of its target social accounts has that tenant ID. `tenantId` is accepted as an alias for parity with GET /v1/social-accounts.\n- `social_account_id` - Filter by social account ID (posts targeting that account). Accepts the encoded ID returned by the API, or a raw numeric ID.\n- `created_after` / `created_before` - Creation timestamp range (ISO 8601)\n- `scheduled_after` / `scheduled_before` - Scheduled timestamp range (ISO 8601)\n- All filters combine with AND.\n\nNote that filters select *posts*: a matching post is returned with its full `socialAccounts` array, so a post fanned out across several tenants will list all of its accounts. When `tenant_id` and `social_account_id` are combined they must be satisfied by the same account, so pairing a tenant with an account outside it returns no results rather than matching through a sibling account.\n\n**Pagination:** use `limit` and `offset`. `pagination.total` is the total number of posts matching the filters across all pages; `pagination.count` is the number of posts in the current page.","tags":["Posts"],"responses":{"200":{"description":"Posts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"posts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"9dyJS"},"orgId":{"type":"string","example":"abc123"},"publishedAt":{"type":["string","null"],"format":"date-time"},"scheduledAt":{"type":["string","null"],"format":"date-time"},"isDraft":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"socialAccounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the social account","example":"9dyJS"},"nickname":{"type":"string","description":"User-defined nickname for this social account"},"network":{"type":"string","description":"Social network identifier (e.g., 'facebook', 'x', 'instagram', 'linkedin', 'threads', 'youtube', 'tiktok')"},"username":{"type":"string","description":"Username or handle on the social network"},"status":{"type":"string","enum":["pending","published","failed","deleted"],"description":"Publishing status for this account. 'pending': awaiting publish (post created or scheduled), 'published': successfully published to this account, 'failed': publishing failed (check error field), 'deleted': post was deleted from the social network platform. Note: A post can partially succeed - some accounts may publish while others fail.","example":"published"},"error":{"type":["string","null"],"description":"Error message if publishing failed. Contains the platform-specific error message. Common errors include: expired access tokens, rate limits, invalid media format, content policy violations. Null if status is not 'failed'.","example":null},"platformPostId":{"type":["string","null"],"description":"The platform-specific post ID after successful publish. Use this for analytics, fetching comments, or deep-linking to the post on the platform. Null if not yet published or if publishing failed.","example":"123456789"},"platformPostUrl":{"type":["string","null"],"description":"The public URL of the post on the social network. Currently populated for Instagram only; null for other networks or for posts created before this feature.","example":"https://www.instagram.com/p/DAbCdEfGhIj/"},"publishedAt":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the post was successfully published to this account. Null if pending or failed. Each account has its own publishedAt since they are processed sequentially.","example":"2025-01-15T10:30:00Z"}},"required":["id","nickname","network","username","status","error","platformPostId","platformPostUrl","publishedAt"]}},"containers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"8xKmL"},"content":{"type":"string"},"media":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"filename":{"type":"string"}},"required":["url","filename"]}}},"required":["id","content","media"]}}},"required":["id","orgId","publishedAt","scheduledAt","isDraft","createdAt","socialAccounts","containers"]},"description":"Array of posts (kept for backward compatibility)"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"9dyJS"},"orgId":{"type":"string","example":"abc123"},"publishedAt":{"type":["string","null"],"format":"date-time"},"scheduledAt":{"type":["string","null"],"format":"date-time"},"isDraft":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"socialAccounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the social account","example":"9dyJS"},"nickname":{"type":"string","description":"User-defined nickname for this social account"},"network":{"type":"string","description":"Social network identifier (e.g., 'facebook', 'x', 'instagram', 'linkedin', 'threads', 'youtube', 'tiktok')"},"username":{"type":"string","description":"Username or handle on the social network"},"status":{"type":"string","enum":["pending","published","failed","deleted"],"description":"Publishing status for this account. 'pending': awaiting publish (post created or scheduled), 'published': successfully published to this account, 'failed': publishing failed (check error field), 'deleted': post was deleted from the social network platform. Note: A post can partially succeed - some accounts may publish while others fail.","example":"published"},"error":{"type":["string","null"],"description":"Error message if publishing failed. Contains the platform-specific error message. Common errors include: expired access tokens, rate limits, invalid media format, content policy violations. Null if status is not 'failed'.","example":null},"platformPostId":{"type":["string","null"],"description":"The platform-specific post ID after successful publish. Use this for analytics, fetching comments, or deep-linking to the post on the platform. Null if not yet published or if publishing failed.","example":"123456789"},"platformPostUrl":{"type":["string","null"],"description":"The public URL of the post on the social network. Currently populated for Instagram only; null for other networks or for posts created before this feature.","example":"https://www.instagram.com/p/DAbCdEfGhIj/"},"publishedAt":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the post was successfully published to this account. Null if pending or failed. Each account has its own publishedAt since they are processed sequentially.","example":"2025-01-15T10:30:00Z"}},"required":["id","nickname","network","username","status","error","platformPostId","platformPostUrl","publishedAt"]}},"containers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"8xKmL"},"content":{"type":"string"},"media":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"filename":{"type":"string"}},"required":["url","filename"]}}},"required":["id","content","media"]}}},"required":["id","orgId","publishedAt","scheduledAt","isDraft","createdAt","socialAccounts","containers"]},"description":"Array of posts (for UI package compatibility)"},"pagination":{"type":"object","properties":{"limit":{"type":"number"},"offset":{"type":"number"},"total":{"type":"number","description":"Total number of posts matching the filters, across all pages"},"count":{"type":"number","description":"Number of posts in this page"}},"required":["limit","offset","total","count"]}},"required":["success","posts","pagination"]}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"parameters":[{"in":"query","name":"social_account_id","schema":{"type":"string","example":"9dyJS"},"description":"Filter by social account ID. Accepts the encoded ID returned by the API, or a raw numeric ID."},{"in":"query","name":"tenant_id","schema":{"type":"string","example":"tenant_123"},"description":"Filter by tenant ID for multi-tenant applications (exact match). Returns posts targeting at least one social account with this tenant ID."},{"in":"query","name":"tenantId","schema":{"type":"string","example":"tenant_123"},"description":"Alias for `tenant_id`, accepted for parity with GET /v1/social-accounts. If both are provided, `tenant_id` wins."},{"in":"query","name":"created_after","schema":{"type":"string","format":"date-time","example":"2025-01-01T00:00:00Z"},"description":"Filter posts created after this timestamp"},{"in":"query","name":"created_before","schema":{"type":"string","format":"date-time","example":"2025-12-31T23:59:59Z"},"description":"Filter posts created before this timestamp"},{"in":"query","name":"scheduled_after","schema":{"type":"string","format":"date-time","example":"2025-01-01T00:00:00Z"},"description":"Filter posts scheduled after this timestamp"},{"in":"query","name":"scheduled_before","schema":{"type":"string","format":"date-time","example":"2025-12-31T23:59:59Z"},"description":"Filter posts scheduled before this timestamp"},{"in":"query","name":"limit","schema":{"type":"string","default":"50","example":"50"},"description":"Number of posts per page (1-100)"},{"in":"query","name":"offset","schema":{"type":"string","default":"0","example":"0"},"description":"Pagination offset"}]}},"/v1/posts/{id}":{"patch":{"operationId":"patchV1PostsById","summary":"Update a post","description":"Update a post that has not been published yet. Every field is optional - only the fields present in the request body are changed. Editable fields are the post body (`content` or `containers`, including each container's `media`), the publish time (`scheduledAt`), the `isDraft` flag, and the platform-specific configuration provided as top-level keys named after the network (`tiktok`, `youtube`, `instagram`, `threads`, `google_business`, `pinterest`, `linkedin`, `facebook`, `vimeo`, `reddit`, `x`, `bluesky`), including each block's per-network `content` override. Platform config is merged per network and per field within a network: sending only `tiktok` leaves the stored YouTube configuration alone, and sending only `tiktok.disableComment` leaves the stored TikTok `postMode` and `privacyLevel` alone. Sending `content` or `containers` **replaces** the post's containers and their media entirely - there is no per-container patching. The set of social accounts a post targets cannot be changed (the accounts carry per-account publish state); delete the post and create a new one to publish to different accounts. Changing `scheduledAt` reschedules the queued publishing job: the existing job is cancelled and a new one is created for the new time, and the response reports `rescheduled: true`. Setting `isDraft` to true cancels the queued job without creating a new one. Posts that are already published (fully or for any single account) are rejected with 409, because editing the stored post does not change anything already live on the platforms. When the request replaces the containers of a post that targets a Threads account, each container must be 500 characters or fewer or the request is rejected with 400 (a `threads.content` override is checked in place of the shared body); a request that leaves the containers alone only length-checks the `<network>.content` overrides it sends. To retry a quota-denied YouTube destination on a partially published post, create a new YouTube-only post targeting that failed account. Rescheduling does not reserve upload capacity; admission is checked when the new attempt executes. Google Business EVENT and OFFER posts require an event title, startDate, startTime, endDate and endTime. CALL buttons omit callToAction.url; other action types require it. Google ignores callToAction on OFFER posts.","tags":["Posts"],"responses":{"200":{"description":"Post updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the post update was successful","example":true},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal warnings about the request. Used to report keys in the request body that were not recognised and were therefore ignored - both top-level keys and keys inside a platform-config block. Unrecognised keys are dropped before the post is stored, so a warning here means the change you sent is not in effect. Present only when there is at least one warning.","example":["Ignored unrecognised key 'privacy_level' in 'tiktok'. Did you mean 'privacyLevel'?"]},"rescheduled":{"type":"boolean","description":"Present when the update changed the publishing schedule. True when the queued publishing job was cancelled and re-created for the new time. Absent when the schedule was untouched.","example":true},"post":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the created post (sqids encoded)","example":"9dyJS"},"orgId":{"type":"string","description":"Organization ID that owns this post","example":"abc123"},"publishedAt":{"type":["string","null"],"format":"date-time","description":"When the post was published, null if not yet published","example":null},"scheduledAt":{"type":["string","null"],"format":"date-time","description":"When the post is scheduled to be published. If empty, the post is immediately published.","example":"2025-09-20T14:00:00Z"},"isDraft":{"type":"boolean","description":"Whether this is a draft post","example":false},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the post was created","example":"2025-01-15T10:30:00Z"},"socialAccounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The account's encoded id - the same value GET /v1/social-accounts returns and the same value you put in the request's `accounts` array. This array lists exactly the accounts that resolved, so comparing these ids against the ids you sent is how you detect entries that were silently ignored. Note this is targeting, not publishing: per-account publish status arrives on GET /v1/posts/:id, not here.","example":"Kx7vQ"},"nickname":{"type":"string","description":"User-defined nickname for this social account","example":"My Company"},"network":{"type":"string","description":"Social network identifier (e.g., 'facebook', 'x', 'instagram', 'linkedin', 'threads', 'youtube', 'tiktok')","example":"x"},"username":{"type":"string","description":"Username or handle on the social network","example":"mycompany"}},"required":["id","nickname","network","username"],"description":"Social account associated with the post"},"description":"Array of social accounts where this post will be published","example":[]},"containers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the container (sqids encoded)","example":"8xKmL"},"content":{"type":"string","description":"The text content of this post container","example":"Check out our new product launch! #excited"},"media":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier for the media file","example":123},"url":{"type":"string","format":"uri","description":"Publicly accessible HTTPS URL to the media file","example":"https://media.yoursite.com/images/launch.jpg"},"filename":{"type":"string","minLength":1,"description":"Clean filename with extension","example":"launch-photo.jpg"}},"required":["id","url","filename"],"description":"Media attachment in the response"},"description":"Array of media attachments for this container","example":[]}},"required":["id","content","media"],"description":"A post container in the response"},"description":"Array of post containers with content and media","example":[]},"networkOverrideConfiguration":{"type":["object","null"],"additionalProperties":{},"description":"The stored platform-specific configuration, keyed by network (e.g. 'tiktokConfiguration', 'youtubeConfiguration'). Echoed back from what was persisted - note the request accepts these as top-level keys named after the network ('tiktok', 'youtube', ...), so the request and response shapes differ. Null when no platform config was provided.","example":null},"scheduled":{"type":"boolean","description":"Whether a publishing job is actually queued for this post. This is the field to check to confirm a post will publish - `true` means the job exists, `false` means nothing will publish it until it is rescheduled (PATCH a `scheduledAt`). Drafts are never queued, so they report `false` by design. Prefer this over inspecting internal fields.","example":true}},"required":["id","orgId","publishedAt","scheduledAt","isDraft","createdAt","socialAccounts","containers","scheduled"],"description":"The post object","example":{"id":"9dyJS","orgId":"abc123","publishedAt":null,"scheduledAt":"2025-09-20T14:00:00Z","isDraft":false,"createdAt":"2025-01-15T10:30:00Z","scheduled":true,"socialAccounts":[{"id":"Kx7vQ","nickname":"My Company","network":"x","username":"mycompany"}],"containers":[{"id":"8xKmL","content":"Check out our new product launch! #excited","media":[{"id":123,"url":"https://media.yoursite.com/images/launch.jpg","filename":"launch-photo.jpg"}]}]}}},"required":["success","post"],"description":"Successful post update response"}}}},"400":{"description":"Invalid post id, invalid request payload, a container that exceeds a targeted network's character limit (Threads: 500), or an attempt to change immutable fields such as the target accounts","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"409":{"description":"The post has already been published and can no longer be edited","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error or failed to reschedule publishing","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"containers":{"type":"array","items":{"type":"object","properties":{"content":{"type":"string","default":"","description":"The text content of this post container. Optional when the container carries media - omit it for a media-only post, and for Facebook/Instagram Stories, which are published without a caption. Can include platform-specific formatting (e.g., hashtags, mentions) but it's not guaranteed to be rendered correctly by all platforms. Threads rejects containers longer than 500 characters, so a post targeting a Threads account (including when cross-posting alongside other networks) is rejected with 400 if any container exceeds it.","example":"Check out our new product launch! #excited"},"media":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Publicly accessible HTTPS URL to the media file. Requirements: (1) Must be publicly accessible - Outstand downloads media server-side, (2) Must use HTTPS, (3) Must return valid Content-Type header, (4) Must be available at publish time - media is fetched when publishing, not when creating the post, (5) Must not require authentication. Supported formats vary by platform: Facebook/X/LinkedIn support JPEG, PNG, GIF; Instagram/Threads support JPEG, PNG; Videos (MP4, MOV) supported on most platforms.","example":"https://media.yoursite.com/images/launch.jpg"},"filename":{"type":"string","minLength":1,"description":"Clean filename with extension (e.g., 'photo.jpg', 'video.mp4'). Do not include query parameters or URL fragments. The extension helps determine media type for platform-specific processing.","example":"launch-photo.jpg"},"altText":{"type":"string","description":"Per-image alternative text for accessibility. Currently applied on Bluesky, which supports alt text per image (app.bsky.embed.images). Other platforms use their own network-specific alt-text configuration and ignore this field.","example":"A product photo showing the new dashboard on a laptop screen"}},"required":["url","filename"],"description":"Media attachment for a post container. Outstand handles media processing per platform: Facebook media is downloaded and re-uploaded as binary data; Instagram/Threads URLs are passed to their container APIs; X media is downloaded and uploaded via their media API; LinkedIn URLs are passed directly; YouTube/TikTok videos are downloaded and uploaded via their respective upload APIs."},"default":[],"description":"Array of media attachments for this container. Multiple media files can be attached per container.","example":[]}},"description":"A single content container within a post. Posts can have multiple containers. A container can have multiple media attachments. Each container that is not the root container, will be published as a reply to the root container."},"description":"Array of post containers. Each container represents a combo for the post content with its own text and media. Use this if you want to create a post with an automated reply to the root container. Otherwise, use the 'content' field.","example":[{"content":"Our newest feature is live! Check it out 🚀","media":[{"url":"https://example.com/feature.png","filename":"feature.png"}]}]},"content":{"type":"string","description":"Simple text content for the post. Use this for single-container posts. Either 'content' or 'containers' must be provided. If you use 'containers', the first container will be the root container and the rest will be published as replies to the root container. Threads rejects content longer than 500 characters, so a post targeting a Threads account (including when cross-posting alongside other networks) is rejected with 400 if it exceeds that - unless `threads.content` supplies a Threads-only body, which is what gets checked instead. To publish different text on one network, put `content` inside that network's block (e.g. `linkedin.content`); this field is then the fallback for every network without one.","example":"Excited to announce our latest update!"},"threads":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Threads instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Threads accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. The 500-character Threads limit is checked against this override instead of the shared body, so a longer shared body can still cross-post to Threads.","example":"A more conversational take for Threads"},"countries":{"type":"array","items":{"type":"string"},"description":"A list of valid ISO 3166-1 alpha-2 country codes that represents the countries where this media should be shown. If this parameter is passed in, the post will not be shown to Threads profiles in countries outside of this list.","example":["US","DK"]},"reply_control":{"type":"string","enum":["everyone","accounts_you_follow","mentioned_only"],"description":"Controls who can reply to this Threads post. Set at publish time only - it cannot be changed after the post goes live. Defaults to 'everyone'.","example":"accounts_you_follow"}},"description":"Custom and override configuration for Threads content. Not to be confused with threaded content or anything other than the Threads By Instagram - the social network."},"instagram":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Instagram instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Instagram accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Takes precedence over the older 'caption' field.","example":"Casual Instagram caption"},"publishAsStory":{"type":"boolean","description":"When true, publish as an Instagram Story instead of a feed post. Stories support a single image or video (no carousel), no caption, and expire after 24 hours. Recommended aspect ratio: 9:16.","example":true},"locationId":{"type":"string","description":"The ID of the location to publish the post to.","example":"1234567890"},"reelThumbOffset":{"type":"number","description":"The offset in milliseconds for the thumbnail of the Reel. Not setting this will use the default thumbnail offset for the Reel, which is 500msec. Ignored if reelCoverUrl is set.","example":1000},"reelCoverUrl":{"type":"string","format":"uri","description":"Public URL of a JPEG image to use as the cover image of the Reel. Reels only - not supported for images, carousels or Stories. Instagram fetches the image server-side, so it must be publicly reachable. Max 8MB, sRGB, 9:16 aspect ratio recommended. Takes precedence over reelThumbOffset.","example":"https://cdn.example.com/reel-cover.jpg"},"userTags":{"type":"array","items":{"type":"object","properties":{"username":{"type":"string"},"x":{"type":"number"},"y":{"type":"number"}},"required":["username","x","y"]},"description":"Array of user tags to add to the post","example":[{"username":"@instagram","x":0.5,"y":0.5}]},"altText":{"type":"string","description":"The alt text for the post - overrides the alt text in the post content/first container.","example":"A picture of our latest product"},"caption":{"type":"string","description":"The caption for the post - overrides the caption in the post content/first container.","example":"Check out our new product! #excited"},"collaborators":{"type":"array","items":{"type":"string"},"maxItems":3,"description":"Array of up to 3 public Instagram usernames to invite as collaborators. Collaborators receive an invite; once accepted, the post appears on both profiles. Only supported for feed posts (IMAGE) and Reels, not Stories.","example":["partner_account","brand_account"]},"isAiGenerated":{"type":"boolean","description":"Self-disclosure that the media is AI-generated, surfaced as Instagram's \"AI info\" label. Applies to feed posts, Reels, carousels and Stories. For carousels the flag is set on the carousel itself, not the individual items.","example":true},"trialReel":{"type":"object","properties":{"graduationStrategy":{"type":"string","enum":["MANUAL","SS_PERFORMANCE"],"description":"MANUAL: the creator graduates the trial reel to followers manually in the Instagram app. SS_PERFORMANCE: Meta automatically graduates the trial reel if it performs well with non-followers.","example":"MANUAL"}},"required":["graduationStrategy"],"description":"Publish this Reel as a Trial Reel, shared only to non-followers until graduated. Requires a single video published as a Reel - not supported for Stories, carousels, or images.","example":{"graduationStrategy":"MANUAL"}}},"description":"Custom and override configuration for Instagram content.","example":{"locationId":"1234567890","reelThumbOffset":3240}},"youtube":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on YouTube instead of the shared post body. Replaces the top-level 'content' (the first container's text) for YouTube accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Becomes the video description; 'title' still applies.","example":"A longer description for the YouTube video."},"isShort":{"type":"boolean","description":"Whether the video is a YouTube Short.","example":true},"categoryId":{"type":"string","description":"The ID of the category to publish the post to.","example":"1234567890"},"privacyStatus":{"type":"string","enum":["public","private","unlisted"],"description":"The privacy status of the post.","example":"public"},"madeForKids":{"type":"boolean","description":"Whether the video is made for kids.","example":true},"containsSyntheticMedia":{"type":"boolean","description":"Self-disclosure that the video contains realistic altered or synthetic content, for example AI-generated video. Maps to YouTube's status.containsSyntheticMedia and surfaces as an altered-content label on the video. Omitted from the upload entirely when not set - Outstand never declares this on your behalf. Cannot be changed through Outstand once the video is published.","example":true},"tags":{"type":"array","items":{"type":"string"},"description":"The tags for the post.","example":["tag1","tag2"]},"title":{"type":"string","description":"The title of the post.","example":"My new video"},"thumbnailUrl":{"type":"string","format":"uri","description":"Public URL of a JPEG or PNG image to set as the video's custom thumbnail. Max 50MB; 16:9 recommended for videos (3840x2160, at least 640px wide) and 9:16 for Shorts. The image is downloaded and checked before the video is uploaded, so an unreachable, oversized or non-JPEG/PNG image fails the publish. Setting it on YouTube is best-effort: if YouTube rejects it - for example because the channel is not verified for custom thumbnails - the video is still published with an auto-generated thumbnail. YouTube may ignore custom thumbnails on Shorts.","example":"https://cdn.example.com/video-thumbnail.jpg"}},"description":"Custom and override configuration for YouTube content."},"tiktok":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on TikTok instead of the shared post body. Replaces the top-level 'content' (the first container's text) for TikTok accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. For photo posts, 'title' and 'description' still take precedence when set.","example":"Hook-first caption for TikTok"},"postMode":{"type":"string","enum":["DIRECT_POST","MEDIA_UPLOAD"],"description":"The posting mode for TikTok. Defaults to MEDIA_UPLOAD when omitted. MEDIA_UPLOAD delivers the media to the creator's TikTok inbox as a draft - they finish the caption, visibility and settings in the TikTok app and publish it themselves - and is not subject to TikTok's 24-hour cap on distinct publishing creators per app. DIRECT_POST publishes straight to the profile, requires privacyLevel, and does count against that cap (reached_active_user_cap).","example":"MEDIA_UPLOAD"},"privacyLevel":{"type":"string","enum":["PUBLIC_TO_EVERYONE","MUTUAL_FOLLOW_FRIENDS","FOLLOWER_OF_CREATOR","SELF_ONLY"],"description":"The privacy level for the TikTok post. Required when postMode is DIRECT_POST - must come from the creator_info privacy_level_options. Ignored for MEDIA_UPLOAD, where the creator picks visibility in the TikTok app.","example":"PUBLIC_TO_EVERYONE"},"title":{"type":"string","maxLength":90,"description":"Photo post title (the bold header), mapped to TikTok's post_info.title. Max 90 UTF-16 runes. Photo posts only. When omitted, the post content is used and truncated to 90 characters.","example":"Summer collection drop"},"description":{"type":"string","maxLength":4000,"description":"Photo post description (the caption), mapped to TikTok's post_info.description. Max 4000 UTF-16 runes. Photo posts only. When omitted, the post content is used.","example":"Our new summer collection is here. Swipe through to see every look."},"disableComment":{"type":"boolean","description":"Whether to disable comments on the TikTok post. Applies to both video and photo posts.","example":false},"disableDuet":{"type":"boolean","description":"Whether to disable duets on the TikTok post. Video posts only - ignored for photo posts (TikTok does not support Duet on photos).","example":false},"disableStitch":{"type":"boolean","description":"Whether to disable stitches on the TikTok post. Video posts only - ignored for photo posts (TikTok does not support Stitch on photos).","example":false},"videoCoverTimestampMs":{"type":"integer","minimum":0,"description":"Timestamp in milliseconds of the video frame to use as the cover, mapped to TikTok's post_info.video_cover_timestamp_ms. DIRECT_POST video posts only - ignored for MEDIA_UPLOAD (the creator picks the cover in the TikTok app) and for photo posts. When omitted, TikTok picks the default cover.","example":3500},"autoAddMusic":{"type":"boolean","description":"Whether TikTok should automatically add recommended music to the post. Applies to photo posts only (ignored for video posts). Defaults to false when not set.","example":false},"brandContentToggle":{"type":"boolean","description":"Branded content disclosure - marks the post as a paid partnership (TikTok UX Guideline Point 3).","example":false},"brandOrganicToggle":{"type":"boolean","description":"Your brand disclosure - marks the post as promotional content for your own brand (TikTok UX Guideline Point 3).","example":false},"isAigc":{"type":"boolean","description":"Whether the content is AI-generated.","example":false}},"description":"Custom and override configuration for TikTok content."},"google_business":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Google Business instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Google Business accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks.","example":"Local-audience wording for Google Business."},"topicType":{"type":"string","enum":["STANDARD","EVENT","OFFER"],"description":"The type of Google Business post. STANDARD is a regular post, EVENT includes event details, OFFER includes promotional offer details.","example":"STANDARD"},"callToAction":{"type":"object","properties":{"actionType":{"type":"string","enum":["BOOK","ORDER","SHOP","LEARN_MORE","SIGN_UP","CALL"],"description":"The type of call-to-action button.","example":"LEARN_MORE"},"url":{"type":"string","format":"uri","description":"Required for non-CALL actions. Omit for CALL; any supplied URL is ignored when publishing.","example":"https://example.com"}},"required":["actionType"],"description":"Call-to-action button configuration. Google ignores this for OFFER posts."},"event":{"type":"object","properties":{"title":{"type":"string","description":"The title of the event.","example":"Grand Opening"},"startDate":{"type":"object","properties":{"year":{"type":"number"},"month":{"type":"number"},"day":{"type":"number"}},"required":["year","month","day"],"description":"The start date of the event.","example":{"year":2026,"month":3,"day":15}},"startTime":{"type":"object","properties":{"hours":{"type":"number"},"minutes":{"type":"number"}},"required":["hours","minutes"],"description":"The start time of the event.","example":{"hours":9,"minutes":0}},"endDate":{"type":"object","properties":{"year":{"type":"number"},"month":{"type":"number"},"day":{"type":"number"}},"required":["year","month","day"],"description":"The end date of the event.","example":{"year":2026,"month":3,"day":15}},"endTime":{"type":"object","properties":{"hours":{"type":"number"},"minutes":{"type":"number"}},"required":["hours","minutes"],"description":"The end time of the event.","example":{"hours":17,"minutes":0}}},"required":["title","startDate","startTime","endDate","endTime"],"description":"Event details with all four schedule fields. Required when topicType is EVENT or OFFER."},"offer":{"type":"object","properties":{"couponCode":{"type":"string","description":"The coupon code for the offer.","example":"SAVE20"},"redeemOnlineUrl":{"type":"string","format":"uri","description":"URL where the offer can be redeemed online.","example":"https://example.com/redeem"},"termsConditions":{"type":"string","description":"Terms and conditions for the offer.","example":"Valid until March 31, 2026."}},"description":"Offer details. Used when topicType is OFFER."}},"description":"Custom and override configuration for Google Business Profile content."},"pinterest":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Pinterest instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Pinterest accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Becomes the pin description; 'title' still applies.","example":"Pin description written for Pinterest search."},"board_id":{"type":"string","minLength":1,"description":"The ID of the Pinterest board to pin to. Required for Pinterest.","example":"123456789"},"cover_image_url":{"type":"string","format":"uri","description":"Cover image URL for video pins. Pinterest requires a cover image for videos; if omitted, the first frame of the video is used automatically.","example":"https://example.com/cover.jpg"},"link":{"type":"string","format":"uri","description":"Destination URL when the pin is clicked.","example":"https://example.com/product"},"alt_text":{"type":"string","maxLength":500,"description":"Alt text for the pin image (max 500 chars).","example":"A red dress on a model"},"title":{"type":"string","maxLength":100,"description":"Pin title (max 100 chars). Defaults to first 100 chars of content.","example":"New Summer Collection"}},"required":["board_id"],"description":"Custom configuration for Pinterest content. board_id is required when publishing to Pinterest."},"vimeo":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Vimeo instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Vimeo accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Becomes the video description; 'title' and 'description' still take precedence when set.","example":"Description for the Vimeo upload."},"title":{"type":"string"},"description":{"type":"string"},"privacyView":{"type":"string","enum":["anybody","unlisted","nobody","password","contacts","users","disable"]},"privacyEmbed":{"type":"string","enum":["public","private","whitelist"]}},"description":"Vimeo-specific video configuration"},"linkedin":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on LinkedIn instead of the shared post body. Replaces the top-level 'content' (the first container's text) for LinkedIn accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Mentions are resolved against this text.","example":"A more professional, detailed version for LinkedIn."},"article":{"type":"object","properties":{"source":{"type":"string","format":"uri","description":"The URL the article card links to. Required. LinkedIn uses this as the primary link for the card.","example":"https://example.com/blog/my-article"},"title":{"type":"string","minLength":1,"maxLength":400,"description":"Title shown on the article card (max 400 chars). Overrides LinkedIn's OG title scraping.","example":"How We Scaled to 1M Users"},"description":{"type":"string","maxLength":4000,"description":"Short description shown beneath the title on the card (max 4000 chars).","example":"A deep dive into our infrastructure journey."},"thumbnailUrl":{"type":"string","format":"uri","description":"Public image URL to use as the card thumbnail. We upload this to LinkedIn's Images API and use the resulting URN. LinkedIn does not accept raw URLs as thumbnails.","example":"https://example.com/og-image.png"}},"required":["source"],"description":"Publish the post as a LinkedIn article link card instead of a plain feed post. The container content becomes the commentary above the card."},"mentions":{"type":"array","items":{"type":"object","properties":{"urn":{"type":"string","pattern":"^urn:li:(organization|person):.+","description":"The URN of the LinkedIn entity to mention. Organizations use 'urn:li:organization:<id>'; members use 'urn:li:person:<id>'.","example":"urn:li:organization:1337"},"text":{"type":"string","minLength":1,"description":"The entity's exact, case-sensitive registered name as it appears on LinkedIn (e.g. 'Outstand.so', not 'Outstand'). This string must also appear verbatim in the post content - the first occurrence is converted into a clickable mention. If the text does not match the entity's full registered name, the API returns an error rather than publishing a silently-broken tag.","example":"LinkedIn"}},"required":["urn","text"]},"description":"Tag LinkedIn members or organizations by mentioning them inline. For each mention, the matching text in the post content is rewritten into LinkedIn's annotation format. Organization text must be the entity's exact, case-sensitive full name - pass the wrong name and the API will reject the request before publishing.","example":[{"urn":"urn:li:organization:1337","text":"LinkedIn"}]},"caption":{"type":"string","minLength":1,"maxLength":400,"description":"Caption shown directly beneath the media in the LinkedIn feed. When omitted no caption is shown - LinkedIn does not add one of its own. Only visible on single-image and single-video posts; multi-image posts accept it but LinkedIn does not render a per-image caption. Ignored for PDF/document posts, which always carry a title, and for article-card posts, which have no media.","example":"Behind the scenes at our Berlin office"}},"description":"Custom configuration for LinkedIn content."},"facebook":{"type":"object","properties":{"videoCoverUrl":{"type":"string","format":"uri","pattern":"^https?:\\/\\/","description":"Public JPEG cover URL, maximum 10,000,000 bytes. Requires one video; unavailable for Stories. Match the video aspect ratio. Validated before video upload; applying the cover after publication is best-effort and failure leaves the video published."},"content":{"type":"string","minLength":1,"description":"Text to publish on Facebook instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Facebook accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Stories reject any caption, so it cannot be combined with publishAsStory.","example":"Facebook-specific wording for the Page post."},"mentions":{"type":"array","items":{"type":"object","properties":{"pageId":{"type":"string","minLength":1,"description":"The numeric Facebook Page ID to tag (e.g. '107934268209008'). The API verifies that this page exists and is accessible before publishing - an invalid or inaccessible ID will cause the post to fail with a clear error.","example":"123456789"},"text":{"type":"string","minLength":1,"description":"The anchor text that marks where the @[pageId] tag is inserted. This string must appear verbatim in the content of a comment container (not the post body) - if it is missing from all comments, the API rejects the request rather than silently skipping the tag. Note: Meta may still not render the tag as a clickable link for pages you do not manage or if the app's 'Page Mentioning' feature has not been approved (a platform limitation that cannot be detected before publishing).","example":"Outstand"}},"required":["pageId","text"]},"description":"Tag other Facebook Pages by mentioning them inline in a comment. IMPORTANT: Meta's Graph API only supports @[pageId] page mentions in comments, not in the main post body. Mentions are applied to comment containers (containers[1+]) - you must include at least one comment container whose text contains the anchor. Only Pages can be tagged; tagging personal profiles is not supported. Meta may silently suppress tags for pages your account has no relationship with.","example":[{"pageId":"123456789","text":"Outstand"}]},"publishAsStory":{"type":"boolean","description":"When true, publish to the Page's Story instead of the feed. Stories take exactly one image or video (no carousel), no caption, no comments and no mentions - the request is rejected rather than silently dropping them, so a Story cannot be cross-posted in the same call as captioned content for other networks. Stories expire after 24 hours, after which analytics return null. Recommended aspect ratio: 9:16 (1080x1920).","example":true},"publishAsReel":{"type":"boolean","description":"When true, publish the video as a Page Reel instead of a plain Page video. Requires exactly one video; the post content is used as the Reel description and comment containers are published as comments. Recommended aspect ratio: 9:16 (1080x1920), 3-90 seconds. Cannot be combined with publishAsStory.","example":true}},"description":"Custom configuration for Facebook content."},"reddit":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Reddit instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Reddit accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Ignored for link posts (when 'url' is set).","example":"Markdown body written for the subreddit."},"subreddit":{"type":"string","minLength":1,"description":"The subreddit to post to, without the 'r/' prefix (e.g. 'programming'). Required for Reddit. Use 'u_<username>' to post to a user profile.","example":"programming"},"title":{"type":"string","minLength":1,"maxLength":300,"description":"The post title (max 300 chars). Required for Reddit - every Reddit post has a title separate from its body.","example":"We built a unified social media API"},"url":{"type":"string","format":"uri","description":"When set, the post is published as a link post pointing at this URL and the container content is ignored (Reddit does not allow a body on link posts). When omitted, the post is a text (self) post with the container content as its markdown body.","example":"https://example.com/blog/launch"},"flairId":{"type":"string","description":"Flair template ID to apply to the post. Some subreddits require flair and auto-remove posts without it.","example":"c4acd8f2-9e2f-11eb-8000-0e3e6a4e8e8a"},"flairText":{"type":"string","maxLength":64,"description":"Custom flair text (max 64 chars), used together with flairId when the flair template allows editable text.","example":"Discussion"},"nsfw":{"type":"boolean","description":"Mark the post as NSFW. Defaults to false.","example":false},"spoiler":{"type":"boolean","description":"Mark the post as a spoiler. Defaults to false.","example":false},"sendReplies":{"type":"boolean","description":"Whether the author receives inbox notifications for replies. Defaults to true.","example":true}},"required":["subreddit","title"],"description":"Custom configuration for Reddit content. subreddit and title are required when publishing to Reddit. Only text (self) and link posts are supported - media containers are rejected."},"x":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on X instead of the shared post body. Replaces the top-level 'content' (the first container's text) for X accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. X's own length limit is not checked at request time.","example":"Short, punchy version for X"}},"description":"Custom configuration for X content. Currently only the per-network content override."},"bluesky":{"type":"object","properties":{"content":{"type":"string","minLength":1,"description":"Text to publish on Bluesky instead of the shared post body. Replaces the top-level 'content' (the first container's text) for Bluesky accounts only - every other network still publishes the shared text, and the top-level 'content' acts as the fallback. Media, first comments and thread replies are not overridden; they stay shared across all networks. Bluesky's 300-character limit is not checked at request time.","example":"A version for Bluesky, within 300 characters"}},"description":"Custom configuration for Bluesky content. Currently only the per-network content override."},"processMedia":{"type":"boolean","description":"Opt in to automatic video processing. When true, video media is inspected and re-encoded to each target network's requirements (container, codec, resolution, frame rate and file size) before publishing, and the correct rendition is sent to each network. Leave unset to publish the file exactly as supplied. Billed per rendition produced, by output duration in 10-second blocks (rounded up, minimum one block per rendition); validation and inspection are always free. Has no effect on posts without video media.","example":true},"scheduledAt":{"type":["string","null"],"format":"date-time","description":"New ISO 8601 timestamp for when the post should be published. Changing this cancels the queued publishing job and creates a new one for the new time. Pass null to clear the schedule - the post is then published on the next queued run. Omit the field to leave the schedule untouched. The maximum time in the future is 30d - a later timestamp is rejected with 400 and the post is left unchanged.","example":"2026-03-01T09:00:00Z"},"isDraft":{"type":"boolean","description":"Whether the post is a draft. Setting this to true cancels any queued publishing job; setting it back to false re-queues the post for its scheduled time (or immediately when there is no scheduledAt).","example":false}},"description":"Schema for updating an existing Post. All fields are optional - only the fields present in the request are changed. Sending 'content' or 'containers' fully replaces the post's containers and their media. The set of social accounts a post targets cannot be changed."}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"get":{"operationId":"getV1PostsById","summary":"Get post details","description":"Get detailed information about a specific post by its ID, including all containers (content segments), attached media files, scheduling status, and publishing state. The post ID is the encoded string identifier returned when creating or listing posts. The response includes the full container hierarchy with media URLs for each segment.\n\nMedia URLs are always the file you uploaded. When a post opts into `processMedia`, videos are normalized in the background and the publish path substitutes the encoded file per network - it never rewrites the source URL stored on the post. Use `containers[].media[].processing` to see that pipeline: the roll-up `status`, the ffprobe reading, each rendition and its public URL, and the per-network `issues` the source would otherwise hit. It is `null` for images and for videos on posts that did not opt in.","tags":["Posts"],"responses":{"200":{"description":"Post retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"post":{"type":"object","properties":{"id":{"type":"string","example":"9dyJS"},"orgId":{"type":"string","example":"abc123"},"publishedAt":{"type":["string","null"],"format":"date-time"},"scheduledAt":{"type":["string","null"],"format":"date-time"},"isDraft":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"socialAccounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the social account","example":"9dyJS"},"nickname":{"type":"string","description":"User-defined nickname for this social account"},"network":{"type":"string","description":"Social network identifier (e.g., 'facebook', 'x', 'instagram', 'linkedin', 'threads', 'youtube', 'tiktok')"},"username":{"type":"string","description":"Username or handle on the social network"},"status":{"type":"string","enum":["pending","published","failed","deleted"],"description":"Publishing status for this account. 'pending': awaiting publish (post created or scheduled), 'published': successfully published to this account, 'failed': publishing failed (check error field), 'deleted': post was deleted from the social network platform. Note: A post can partially succeed - some accounts may publish while others fail.","example":"published"},"error":{"type":["string","null"],"description":"Error message if publishing failed. Contains the platform-specific error message. Common errors include: expired access tokens, rate limits, invalid media format, content policy violations. Null if status is not 'failed'.","example":null},"platformPostId":{"type":["string","null"],"description":"The platform-specific post ID after successful publish. Use this for analytics, fetching comments, or deep-linking to the post on the platform. Null if not yet published or if publishing failed.","example":"123456789"},"platformPostUrl":{"type":["string","null"],"description":"The public URL of the post on the social network. Currently populated for Instagram only; null for other networks or for posts created before this feature.","example":"https://www.instagram.com/p/DAbCdEfGhIj/"},"publishedAt":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the post was successfully published to this account. Null if pending or failed. Each account has its own publishedAt since they are processed sequentially.","example":"2025-01-15T10:30:00Z"}},"required":["id","nickname","network","username","status","error","platformPostId","platformPostUrl","publishedAt"]}},"containers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"8xKmL"},"content":{"type":"string"},"media":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"filename":{"type":"string"},"processing":{"type":["object","null"],"properties":{"status":{"type":"string","enum":["pending","processing","ready","failed"],"description":"Roll-up of the probe and every rendition. `ready` means publishing will use the best available file and will not wait. `pending`/`processing` means a publish attempt defers until the encode settles or the deadline passes. `failed` means the probe or an encode gave up, and publishing falls back to the file you uploaded.","example":"ready"},"sourceStatus":{"type":"string","enum":["pending","probing","probed","probe_failed"],"description":"State of the source inspection. `probe_failed` means we could not read the file at all, so no renditions were planned.","example":"probed"},"error":{"type":["string","null"],"description":"Why the probe failed, when sourceStatus is `probe_failed`.","example":null},"probe":{"type":["object","null"],"properties":{"containerFormat":{"type":"string","example":"mov,mp4,m4a,3gp,3g2,mj2"},"durationMs":{"type":"number","example":70800},"width":{"type":"number","description":"Stored width, before any container rotation is applied.","example":1920},"height":{"type":"number","example":1080},"fps":{"type":"number","example":30},"videoCodec":{"type":"string","example":"h264"},"audioCodec":{"type":["string","null"],"description":"Null for a silent video.","example":"aac"},"sizeBytes":{"type":"number","example":18408940},"rotation":{"type":"number","description":"Display rotation in degrees from container metadata: 0, 90, 180 or 270.","example":0},"moovAtFront":{"type":"boolean","description":"Instagram rejects files whose moov atom trails the media data.","example":true},"hasEditLists":{"type":"boolean","description":"Instagram rejects files with edit lists.","example":false}},"required":["containerFormat","durationMs","width","height","fps","videoCodec","audioCodec","sizeBytes","rotation","moovAtFront","hasEditLists"],"additionalProperties":true,"description":"What ffprobe read off the source. Null until the probe completes."},"renditions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","description":"`master` is the network-agnostic normalized encode. Any other value is a network name, produced because the master would still breach that network's caps.","example":"master"},"status":{"type":"string","enum":["queued","processing","ready","failed"],"example":"ready"},"url":{"type":["string","null"],"description":"Public URL of the encoded file, once ready. This is what the publish path sends to the network in place of your original.","example":"https://media.outstand.so/renditions/abc123/568/master.mp4"},"error":{"type":["string","null"],"example":null}},"required":["kind","status","url","error"]},"description":"An empty array is a success, not a gap: it means the source already satisfied every target network and nothing needed re-encoding."},"issues":{"type":"array","items":{"type":"object","properties":{"level":{"type":"string","enum":["error","warning"],"description":"`error` blocks post creation. `warning` does not - either a rendition fixes it, or you chose to publish as-is.","example":"warning"},"code":{"type":"string","example":"file_too_large"},"message":{"type":"string","example":"Video is 512 MB, above the 100 MB limit for bluesky."},"network":{"type":"string","example":"bluesky"},"fixableByTranscode":{"type":"boolean","description":"False for duration and aspect-ratio breaches - fixing those would mean trimming or cropping your content, which we do not do on your behalf.","example":true}},"required":["level","code","message","network","fixableByTranscode"]},"description":"What the post's target networks object to, derived from the probe. Recomputed on read, so it reflects the accounts the post targets now."}},"required":["status","sourceStatus","error","probe","renditions","issues"],"description":"Video processing state for this file, or null when it is not a tracked video."}},"required":["url","filename","processing"]}},"publishResults":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string","description":"The social account this outcome belongs to.","example":"9dyJS"},"status":{"type":"string","enum":["published","failed"],"example":"published"},"platformCommentId":{"type":["string","null"],"description":"Platform-specific ID of the posted comment/reply.","example":"urn:li:comment:(urn:li:share:123,456)"},"error":{"type":["string","null"],"description":"Error message if this container failed to publish for the account.","example":null},"publishedAt":{"type":"string","format":"date-time","description":"When this outcome was recorded."}},"required":["accountId","status","platformCommentId","error","publishedAt"]},"description":"Per-account publish outcome for non-root containers (first comments/replies). Empty for the root container, whose status is reflected by the post's socialAccounts."}},"required":["id","content","media","publishResults"]}}},"required":["id","orgId","publishedAt","scheduledAt","isDraft","createdAt","socialAccounts","containers"]}},"required":["success","post"]}}}},"403":{"description":"Unauthorized - post does not belong to your organization","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"delete":{"operationId":"deleteV1PostsById","summary":"Delete & cancel a post","description":"Cancel a scheduled post by removing it from the publishing queue and deleting it from the database. If the post is scheduled (not yet published), the publishing task is cancelled first, then the post record is removed. If the post was already published, it is only deleted from the Outstand database - the published content remains on the social media platforms. This action cannot be undone.","tags":["Posts"],"responses":{"200":{"description":"Post cancelled from publishing queue and deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the post cancellation was successful","example":true},"message":{"type":"string","description":"Success message","example":"Post cancelled and deleted successfully"}},"required":["success","message"],"description":"Successful post cancellation response"}}}},"403":{"description":"Unauthorized - post does not belong to your organization. Activity will be recorded.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"404":{"description":"Post not found. Potential reason: post was already deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/posts/{id}/analytics":{"get":{"operationId":"getV1PostsByIdAnalytics","summary":"Get post analytics","description":"Fetch analytics and metrics for a published post across all social accounts it was published to. Returns both per-account metrics (likes, comments, shares, views, impressions, reach, engagement rate) and aggregated totals across all platforms. Only available for posts that have been published - draft or scheduled posts will return a 400 error. Platform-specific metrics (e.g., retweets for X, saves for Instagram) are included in the `platform_specific` field of each account's metrics.\n\nInstagram Stories: `media_product_type` is `\"STORY\"` and the metric set differs from feed/reels - `likes`/`comments`/`saves` are not supported and returned as `null`, while `replies`, `profile_visits`, `follows`, `link_clicks`, `reposts`, `total_interactions` and a `navigation` breakdown (`tap_forward`, `tap_back`, `tap_exit`, `swipe_forward`) are populated instead. Story insights are only queryable for approximately 24 hours after publish; after that window this endpoint returns `metrics: null` with `metrics_error.code: \"metrics_expired\"` for that account.\n\nInstagram Reels: `media_product_type` is `\"REELS\"` and the response adds `average_watch_time_ms`, `total_watch_time_ms` (milliseconds, replays included), `skip_rate` (percentage of views skipped in the first 3 seconds, estimated by Meta), `reposts` and `total_interactions`. Any of these Meta did not report is `null` and named in `platform_specific.unavailable_fields`. Per-reel follows and profile visits are not available from Instagram; use the account-level `follower_growth` from `GET /v1/social-accounts/{id}/metrics` instead. Instagram data can lag up to 48 hours.\n\nFacebook: video and single-photo posts may initially have a media ID. When Meta provides a corresponding Post ID, Outstand resolves and caches it, so `platform_post_id` may differ from the ID returned at publish time. Unsupported or unreported engagement counts are `null` and named in `platform_specific.unavailable_fields`; measured zero remains `0`. Available counts are returned even when other fields or supplementary insights are unavailable. `engagement_total` and `engagement_rate` are `null` when required engagement counts are unavailable. Aggregated totals sum only available measurements and can be partial; engagement-rate averages include measured zeros and exclude unavailable rates.\n\nFacebook video and Reel posts also return video insights: `views`, `average_watch_time_ms` and `total_watch_time_ms` for every video; `total_plays` (including replays), `replays` and `followers_gained` for Reels, where `views` counts plays excluding replays; and `approx_completion_rate` for regular videos, the share of views that reached 97% of the video. Meta exposes no completion metric for Reels, so Reels omit it - use `average_watch_time_ms` and the retention graph instead. Pass `include=retention` to add `retention`, an array of `{ t, pct }` points (at most 41, sampled by Meta) giving the percentage of plays still watching at each position. Video fields Meta did not report are `null` and named in `platform_specific.unavailable_fields`; the video id and raw metric names are under `platform_specific.video`. Reading video insights needs the `read_insights` permission and a Page user with the Analyze task.","tags":["Posts","Analytics"],"responses":{"200":{"description":"Analytics retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"post":{"type":"object","properties":{"id":{"type":"string","example":"9dyJS"},"publishedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","publishedAt","createdAt"]},"metrics_by_account":{"type":"array","items":{"type":"object","properties":{"social_account":{"type":"object","properties":{"id":{"type":"string","description":"Encoded social account ID, the same value returned by GET /v1/social-accounts and in posts[].socialAccounts[].id","example":"un7ME"},"nickname":{"type":"string"},"network":{"type":"string"},"username":{"type":"string"}},"required":["id","nickname","network","username"]},"platform_post_id":{"type":"string"},"platform_post_url":{"type":["string","null"]},"published_at":{"type":"string","format":"date-time"},"metrics":{"type":["object","null"],"properties":{"likes":{"type":["number","null"]},"comments":{"type":["number","null"]},"shares":{"type":["number","null"]},"views":{"type":["number","null"]},"total_plays":{"type":["number","null"],"description":"Facebook Reel: plays including replays. `views` counts plays excluding replays."},"replays":{"type":["number","null"],"description":"Facebook Reel: replay count."},"followers_gained":{"type":["number","null"],"description":"Facebook Reel: Page followers gained from this Reel."},"approx_completion_rate":{"type":["number","null"],"description":"Facebook regular video only: percentage of views that reached 97% of the video. Approximate - Meta has no completion metric for videos, and none for Reels, which omit this field."},"retention":{"type":["array","null"],"items":{"type":"object","properties":{"t":{"type":"number","description":"Position key returned by Meta, ascending (an interval index over the video)."},"pct":{"type":"number","description":"Percentage (0-100) of plays still watching at this position."}},"required":["t","pct"]},"description":"Facebook video/Reel retention graph, returned only with `include=retention`. At most 41 points, sampled by Meta. The last point roughly approximates the completion share. null when Meta did not report it."},"impressions":{"type":["number","null"]},"reach":{"type":["number","null"]},"engagement_rate":{"type":["number","null"]},"engagement_total":{"type":["number","null"]},"media_product_type":{"type":"string","enum":["FEED","REELS","STORY"]},"replies":{"type":"number"},"profile_visits":{"type":"number"},"follows":{"type":"number"},"link_clicks":{"type":"number"},"reposts":{"type":["number","null"]},"total_interactions":{"type":["number","null"]},"average_watch_time_ms":{"type":["number","null"],"description":"Instagram Reels and Facebook videos/Reels: average time spent watching, in milliseconds (ig_reels_avg_watch_time on Instagram; post_video_avg_time_watched or total_video_avg_time_watched on Facebook, replays included on Reels)","example":4210},"total_watch_time_ms":{"type":["number","null"],"description":"Instagram Reels and Facebook videos/Reels: total time spent watching across all plays, in milliseconds, replays included (ig_reels_video_view_total_time on Instagram; post_video_view_time or total_video_view_total_time on Facebook)","example":982000},"skip_rate":{"type":["number","null"],"description":"Instagram Reels: percentage (0-100) of views skipped within the first 3 seconds (reels_skip_rate). Meta marks this metric as estimated and in development.","example":37.5},"navigation":{"type":"object","properties":{"tap_forward":{"type":"number"},"tap_back":{"type":"number"},"tap_exit":{"type":"number"},"swipe_forward":{"type":"number"}},"description":"Instagram Story navigation breakdown (tap_forward, tap_back, tap_exit, swipe_forward)"},"platform_specific":{"type":"object","additionalProperties":{}}},"description":"Analytics metrics for a post on a specific platform"},"metrics_error":{"type":"object","properties":{"code":{"type":"string","enum":["token_expired","scope_missing","post_not_found","metrics_expired","unknown"]},"message":{"type":"string"}},"required":["code","message"],"description":"Reason metrics could not be fetched for this account (present only when metrics is null). metrics_expired means the content (e.g. an Instagram Story) is no longer queryable - Stories only expose insights for ~24h after publish."}},"required":["social_account","platform_post_id","platform_post_url","published_at","metrics"],"description":"Analytics metrics for a post on a specific social account"}},"aggregated_metrics":{"type":"object","properties":{"total_likes":{"type":"number"},"total_comments":{"type":"number"},"total_shares":{"type":"number"},"total_views":{"type":"number"},"total_impressions":{"type":"number"},"total_reach":{"type":"number"},"average_engagement_rate":{"type":"number"}},"required":["total_likes","total_comments","total_shares","total_views","total_impressions","total_reach","average_engagement_rate"],"description":"Sums of available measurements across platforms; totals can be partial. The average includes measured zero rates and excludes unavailable rates."}},"required":["success","post","metrics_by_account","aggregated_metrics"],"description":"Post analytics response with per-account and aggregated metrics"}}}},"400":{"description":"Post is not published (draft or scheduled)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"403":{"description":"Unauthorized - post does not belong to your organization","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"parameters":[{"in":"query","name":"include","schema":{"type":"string","example":"retention"},"description":"Comma-separated list of optional, costlier metrics to add. `retention` adds the video retention graph (`metrics.retention`) for Facebook video and Reel posts, at the cost of one extra Graph API request per video. Ignored for other networks and for non-video posts."},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/posts/{id}/remote":{"delete":{"operationId":"deleteV1PostsByIdRemote","summary":"Delete a post from social networks","description":"Delete a published post from all remote social network platforms it was published to. The post record is kept in the Outstand database but marked as deleted. Returns per-account results - networks that do not support post deletion via API (TikTok, Instagram) will appear with status='failed'. Threads is supported (requires the threads_delete scope) and only removes posts created by the connected account. At least one successful deletion returns HTTP 200 with success=true.","tags":["Posts"],"responses":{"200":{"description":"Remote deletion attempted. Check the 'results' array for per-account status. 'success' is true if at least one network deleted the post.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"True if at least one social network account had its post successfully deleted. False if all accounts failed.","example":true},"results":{"type":"array","items":{"type":"object","properties":{"network":{"type":"string","description":"Social network identifier (e.g., 'x', 'linkedin', 'bluesky')","example":"x"},"username":{"type":"string","description":"Username or handle on the social network","example":"mycompany"},"platform_post_id":{"type":["string","null"],"description":"The platform-specific post ID that was targeted for deletion","example":"1234567890"},"status":{"type":"string","enum":["deleted","failed"],"description":"'deleted': post was successfully removed from the platform and its status is marked deleted. 'failed': deletion failed or the network does not support deleting posts via API (see error field).","example":"deleted"},"error":{"type":["string","null"],"description":"Error message when status is 'failed'. Examples: 'TikTok does not support deleting posts via API', 'X API error 403: Forbidden'.","example":null}},"required":["network","username","platform_post_id","status","error"]},"description":"Per-account deletion results. Networks without a delete API (TikTok, Instagram, Threads) will appear here with status='failed'."}},"required":["success","results"]}}}},"400":{"description":"Post has not been published to any social network yet.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"403":{"description":"Unauthorized - post does not belong to your organization.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"404":{"description":"Post not found.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/posts/{id}/replies/{replyId}":{"delete":{"operationId":"deleteV1PostsByIdRepliesByReplyId","summary":"Delete a comment","description":"Delete a comment (that was created through Outstand) from the social network it lives on. Provide the post ID in the path and the comment's platform-specific ID as replyId. Use the platform_post_id or account_username query param to select which connected account's credentials to use (optional when the post was published to only one account). Supported on Instagram and Threads. On Threads (requires the threads_delete scope) only comments/replies created by the connected account can be deleted - other users' replies can be hidden instead (see the Threads API). Comment deletion is not supported on every network and will return an error where unavailable.","tags":["Posts","Comments"],"responses":{"200":{"description":"Comment deleted successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"True if the comment was successfully deleted from the social network.","example":true}},"required":["success"]}}}},"400":{"description":"Invalid request, or the target network does not support deleting comments via API.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"404":{"description":"Post or matching social account not found.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"parameters":[{"in":"query","name":"platform_post_id","schema":{"type":"string","example":"123456789"},"description":"The platform-specific post ID of the account whose credentials should be used to delete the comment. Optional when the post was published to only one account."},{"in":"query","name":"account_username","schema":{"type":"string","example":"mycompany"},"description":"The username or nickname of the connected account whose credentials should be used. Optional when the post was published to only one account."},{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"replyId","required":true}]}},"/v1/posts/{id}/repost":{"post":{"operationId":"postV1PostsByIdRepost","summary":"Repost a post to social networks","description":"Repost (reshare) a published post to all remote social network platforms it was published to. Optionally pass a 'quote' to add commentary on top of the repost where the network supports it (X quote tweet, Bluesky quote post, LinkedIn reshare with commentary, Threads quote post) - otherwise a plain repost/reshare is created. Returns per-account results. Networks that do not support reposting via API (Facebook, Instagram, Pinterest, TikTok, Google Business, Vimeo, YouTube) will appear with status='failed'. At least one successful repost returns HTTP 200 with success=true.","tags":["Posts"],"responses":{"200":{"description":"Repost attempted. Check the 'results' array for per-account status. 'success' is true if at least one network was reposted to.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"True if at least one social network account was successfully reposted to. False if all accounts failed.","example":true},"results":{"type":"array","items":{"type":"object","properties":{"network":{"type":"string","description":"Social network identifier (e.g., 'x', 'linkedin', 'bluesky')","example":"bluesky"},"username":{"type":"string","description":"Username or handle on the social network","example":"mycompany"},"platform_post_id":{"type":["string","null"],"description":"The platform-specific post ID of the original post that was reposted","example":"at://did:plc:abc123/app.bsky.feed.post/xyz"},"status":{"type":"string","enum":["reposted","failed"],"description":"'reposted': the repost/reshare was created successfully. 'failed': the repost failed, or the network does not support reposting via API (see error field).","example":"reposted"},"repost_id":{"type":["string","null"],"description":"The platform-specific ID of the newly created repost/quote record. Null when status is 'failed'.","example":"at://did:plc:abc123/app.bsky.feed.repost/def456"},"error":{"type":["string","null"],"description":"Error message when status is 'failed'. Examples: 'Reposting is not supported by Instagram's API', 'X API error 403: Forbidden'.","example":null}},"required":["network","username","platform_post_id","status","repost_id","error"]},"description":"Per-account repost results. Networks without a repost API (Facebook, Instagram, Pinterest, TikTok, Google Business, Vimeo, YouTube) will appear here with status='failed'."}},"required":["success","results"]}}}},"400":{"description":"Post has not been published to any social network yet.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"403":{"description":"Unauthorized - post does not belong to your organization.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"404":{"description":"Post not found.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/posts/{id}/replies":{"post":{"operationId":"postV1PostsByIdReplies","summary":"Publish a comment","description":"Create a reply (comment) on a post that was published through Outstand. You can target a specific social account by providing either the platform_post_id (the native post ID on the social network) or the account_username of the connected account. Replies can be made to the root post or to another account's reply/comment under your own post. To thread the reply under a specific comment instead of the post, pass parent_comment_id (the comment's platform ID/URN) - supported on Facebook, Instagram, LinkedIn, and Threads. Facebook, Instagram, and LinkedIn allow a single reply tier (replies to replies collapse onto the top-level comment), while Threads preserves the nesting at any depth. Note: We currently do not support interacting with posts that were not published through Outstand. The post must have been published (not just scheduled) to the target account. Comments are not supported on every network - TikTok, Pinterest, and Google Business Profile do not expose a comments API, and YouTube comments are not yet supported; these networks will return an error.","tags":["Posts","Comments"],"responses":{"200":{"description":"Reply published successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"reply_id":{"type":"string","description":"The platform-specific ID of the published reply","example":"123456789"}},"required":["success","reply_id"]}}}},"400":{"description":"Validation Error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"array","items":{}},"data":{}},"required":["success","error","data"]}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","description":"The content of the reply","example":"Great post! I agree with you."},"platform_post_id":{"type":"string","description":"The platform-specific post ID to reply to (e.g., X post ID, Threads post ID, etc.)","example":"123456789"},"account_username":{"type":"string","description":"The username or nickname of the social account to reply from","example":"mycompany"},"parent_comment_id":{"type":"string","description":"The platform-specific ID (or URN) of a comment to reply to, threading the reply under that comment instead of the post. Supported on Facebook, Instagram, LinkedIn, and Threads. Facebook, Instagram, and LinkedIn allow a single reply tier, so replies to replies are flattened onto the top-level comment; Threads nests them for real, so you can reply to a reply at any depth.","example":"17851234567890123"}},"required":["content"]}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"get":{"operationId":"getV1PostsByIdReplies","summary":"Get post replies/comments","description":"Fetch all comments and replies for a specific published post from a particular social media account. Specify the target account using `network` (required) and optionally `username`. If the post was published to only one account on that network, `username` can be omitted. When the post was published to multiple accounts on the same network, omitting `username` returns a 400 with the available usernames to disambiguate. By default only top-level comments are returned; set `include_replies=true` to nest each comment's replies under it (Facebook, Instagram, LinkedIn - a single reply tier; Threads nests to any depth). Note that Threads returns the whole conversation flat when `include_replies` is omitted, so nested replies still appear there as top-level entries. Comments are not supported on every network - TikTok, Pinterest, and Google Business Profile do not expose a comments API, and YouTube comments are not yet supported; these networks will return an error.\n\n**Response keys:** read `data`, which returns the same comment shape for every network (id, author, text, created_at, like_count, and nested `replies` when requested). The `replies` key is deprecated: it preserves the per-network shape this endpoint returned before the cross-network format existed, so it differs by network and never carries nested replies. It is kept only so existing integrations do not break.","tags":["Posts","Comments"],"responses":{"200":{"description":"Replies retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"replies":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/LegacyNormalizedReply"}},{"$ref":"#/components/schemas/LegacyFacebookReplies"},{"$ref":"#/components/schemas/LegacyInstagramReplies"},{"type":"array","items":{"$ref":"#/components/schemas/LegacyBlueskyReply"}},{"type":"array","items":{"$ref":"#/components/schemas/LegacyVimeoComment"}}],"deprecated":true,"description":"Deprecated. The per-network shape this endpoint returned before the cross-network reply format was introduced, frozen so existing integrations keep working. The shape depends on `network`: Facebook and Instagram return an object `{ comments: [...] }`; Bluesky and Vimeo return arrays with network-specific field names (note Bluesky's `replies` here is a reply count, not an array); every other network returns the same array as `data`. Nested replies never appear here, even with include_replies=true. Use `data`, which is identical across every network."},"data":{"type":"array","items":{"$ref":"#/components/schemas/NormalizedReply"},"description":"Top-level comments in the cross-network shape, each optionally carrying nested replies when include_replies=true. Identical for every network. Nesting is one tier deep on Facebook, Instagram, and LinkedIn, and unbounded on Threads. Prefer this over `replies`."}},"required":["success","replies","data"]}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"403":{"description":"Unauthorized - post belongs to different organization","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"404":{"description":"Post not found or account not published","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Invalid payload"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_key_reuse"},"details":{"example":{"content":{"_errors":["Required"]}}}},"required":["success","error"],"description":"Error response"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string","example":"Internal server error"},"code":{"type":"string","description":"Stable machine-readable error code, when one applies","example":"idempotency_recovery_failed"},"message":{"type":"string","example":"Database connection failed"}},"required":["success","error"],"description":"Internal server error response"}}}}},"parameters":[{"in":"query","name":"network","schema":{"type":"string","minLength":1,"example":"x"},"required":true,"description":"Social network name (e.g., 'x', 'threads')"},{"in":"query","name":"username","schema":{"type":"string","minLength":1,"example":"mycompany"},"description":"Username or nickname of the social account that published the post. Optional when the post was published to only one account on the given network."},{"in":"query","name":"include_replies","schema":{"type":"boolean","example":"true"},"description":"When 'true', nest each comment's replies under it (post → comment → replies). Defaults to top-level comments only. Fetching replies on LinkedIn issues an extra API call per comment."},{"in":"query","name":"resolve_author_names","schema":{"type":"boolean","example":"true"},"description":"LinkedIn only: when 'true', resolve each commenter's URN into `author_name` (plus `author_username`, `author_avatar_url` and `author_profile_url` where LinkedIn provides them). Every other network already returns a readable author and ignores this. Some LinkedIn commenters cannot be resolved (for example members who limit their off-LinkedIn visibility) - those come back with `author_name: null` and `author` left as the URN. LinkedIn does not allow member profile data to be stored: cache a resolved name for at most 24 hours and re-fetch after that."},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}}}}