{"openapi":"3.1.0","info":{"title":"Post Social API","version":"1.0.0","description":"Publish and schedule posts to connected social accounts. Authenticate with an API key: Authorization: Bearer ps_live_... (test keys, ps_test_..., cannot publish)."},"servers":[{"url":"https://www.postsocial.xyz/api"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}},"paths":{"/v1/me":{"get":{"operationId":"get_workspace","summary":"Who am I","description":"Shows which Post Social workspace this key acts for, the key's name, and whether it is a live or test key (test keys cannot publish).","parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts":{"get":{"operationId":"list_social_accounts","summary":"List connected accounts","description":"Lists the social accounts connected to Post Social, with their ids, health, profile and what each can post (capabilities and limits).","parameters":[{"name":"platform","in":"query","required":false,"schema":{"type":"string","enum":["instagram","facebook","threads","youtube","tiktok","linkedin","bluesky","x"],"description":"Only this platform."}},{"name":"profile_id","in":"query","required":false,"schema":{"type":"string","description":"Only accounts in this profile (an id from list_profiles), or none for accounts in no profile."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/connect-link":{"post":{"operationId":"request_connect_link","summary":"Get a link to connect an account","description":"Returns a link the person opens to connect a social account in Post Social. Accounts can only be connected by the person, never by an AI.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","enum":["instagram","facebook","threads","youtube","tiktok","linkedin","bluesky","x"]},"profile_id":{"type":"string","format":"uuid","description":"The profile the new account goes into (from list_profiles). Optional."}},"required":["platform"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/profiles":{"get":{"operationId":"list_profiles","summary":"List profiles","description":"Lists the profiles accounts are grouped under (a business, organisation or client), with how many connected accounts each has. Filter list_social_accounts by profile_id to see a profile's accounts.","parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create_profile","summary":"Create a profile","description":"Creates a profile to group accounts under, such as a business, organisation or client.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60,"description":"The profile's name, unique in the workspace."},"description":{"type":"string","maxLength":280},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","description":"A colour like #9B6CFF."},"timezone":{"type":"string","description":"An IANA time zone, e.g. America/New_York."}},"required":["name"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/profiles/{profile_id}":{"patch":{"operationId":"update_profile","summary":"Edit a profile","description":"Changes a profile's name, description, colour or time zone. Leave out what stays the same.","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The profile id."}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60},"description":{"type":"string","maxLength":280},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"timezone":{"type":"string"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete_profile","summary":"Delete a profile","description":"Deletes a profile. Its accounts stay connected and move to no profile.","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The profile id."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{account_id}/profile":{"post":{"operationId":"move_account_to_profile","summary":"Move an account to a profile","description":"Puts a connected account in a profile, or in no profile when profile_id is none.","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The connected account id."}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profile_id":{"type":"string","description":"The profile id (from list_profiles), or none for no profile."}},"required":["profile_id"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{account_id}/tiktok-delivery":{"post":{"operationId":"set_tiktok_delivery","summary":"Choose how AI posts reach a TikTok account","description":"Sets how this TikTok account receives AI posts: direct (the default) posts straight away; inbox sends each post to the creator's TikTok inbox to finish and post in TikTok. Posts that leave delivery_mode out follow this; an account set to inbox refuses direct posts. Change it only when the person asks.","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The connected TikTok account id."}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"delivery":{"type":"string","enum":["direct","inbox"]}},"required":["delivery"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/media":{"get":{"operationId":"list_media","summary":"List media","description":"Lists uploaded and imported media, newest first. Only media with status ready can be posted.","parameters":[{"name":"media_type","in":"query","required":false,"schema":{"type":"string","enum":["image","video"]}},{"name":"include_hidden","in":"query","required":false,"schema":{"type":"boolean","default":false}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25,"description":"How many to return (1 to 100)."}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"description":"How many to skip, for paging."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/media/{media_id}":{"get":{"operationId":"get_media","summary":"Get one media item","description":"Gets a media item's details and status. Ready items include a temporary view link (valid 1 hour).","parameters":[{"name":"media_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The media id."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete_media","summary":"Remove media from the library","description":"Removes a media item from the library. Files used by a post are kept until that post no longer needs them; unused files are deleted automatically later.","parameters":[{"name":"media_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The media id."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/media/import":{"post":{"operationId":"import_media","summary":"Import media from a link","description":"Copies an image or video from a public https link into Post Social. It is checked in the background; poll get_media until status is ready (or failed, with a reason).","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"A public https link to the file."},"name":{"type":"string","maxLength":200}},"required":["url"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/media/uploads":{"post":{"operationId":"start_media_upload","summary":"Start uploading a file","description":"For tools that can send files (CLI, scripts). Returns upload links: PUT each part's bytes to its url, keep each response's ETag, then call finish_media_upload. Links last 1 hour. single_part: true returns one link for the whole file (for tools that send a file in one request, like Apple Shortcuts).","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file_name":{"type":"string","maxLength":200},"mime_type":{"type":"string","description":"image/jpeg, image/png, image/webp, video/mp4 or video/quicktime. Left out, taken from the file name."},"size_bytes":{"type":"integer","minimum":1,"description":"Required, except with single_part: then the stored size is checked when the upload finishes."},"single_part":{"type":"boolean","description":"One upload link for the whole file instead of parts."}},"required":["file_name"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/media/{media_id}/complete":{"post":{"operationId":"finish_media_upload","summary":"Finish an upload","description":"Completes an upload started with start_media_upload. The file is then checked in the background; poll get_media until status is ready. parts can be left out when the tool can't read ETags; Post Social then reads them from storage.","parameters":[{"name":"media_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The media id."}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"parts":{"type":"array","items":{"type":"object","required":["part_number","etag"],"properties":{"part_number":{"type":"integer"},"etag":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/posts/validate":{"post":{"operationId":"validate_post","summary":"Check a post before sending","description":"Checks a proposed post against every chosen platform's rules (caption length, media shape and length, post types) without saving anything. Returns plain-language problems per account. Each account is named by its profile name (account), its platform and its handle (account_handle, without the @; null for LinkedIn, Facebook Pages and YouTube, which share no handle with Post Social), so two accounts with the same name can be told apart.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"caption":{"type":"string","maxLength":10000,"description":"The shared caption. Each platform's limit is checked (see list_social_accounts capabilities)."},"media_ids":{"type":"array","maxItems":35,"items":{"type":"string","format":"uuid"},"description":"Media to attach, in order (ids from list_media, import_media or an upload)."},"destinations":{"type":"array","minItems":1,"maxItems":20,"description":"Where the post goes: one entry per connected account (ids from list_social_accounts).","items":{"type":"object","required":["account_id"],"properties":{"account_id":{"type":"string","format":"uuid","description":"The connected account id."},"options":{"type":"object","description":"Per-platform settings. media_type is required for Instagram, Facebook and Threads. instagram: media_type image | reel | carousel; caption (overrides the shared caption). facebook: media_type text | link | image | reel | video; message (overrides caption); link (for link posts); title (videos). threads: media_type text | image | video | carousel; text (overrides caption). youtube (Shorts, one vertical video up to 3 min): title (required, up to 100 characters); description; privacy_status public | unlisted | private (required). Video covers (any platform's options): cover_media_id (an image from list_media) or cover_time_ms (a frame, in milliseconds from the start), not both. Instagram Reels and YouTube take either; TikTok direct posts take a frame only; LinkedIn videos take either; TikTok drafts, Facebook, Threads, Bluesky and X can't set a cover. Instagram crops the cover to a centred square in the profile grid. tiktok: delivery_mode direct (posts straight away; needs privacy_level PUBLIC_TO_EVERYONE | MUTUAL_FOLLOW_FRIENDS | FOLLOWER_OF_CREATOR | SELF_ONLY, the audience the person asked for, never assumed) or inbox (a draft in the creator's TikTok inbox, where they finish it and tap Post); left out, it follows the account's tiktok_delivery from list_accounts (direct unless the person chose inbox; set_tiktok_delivery changes it); media_type video (one video) | photo (1 to 35 images as a swipeable carousel; JPEG, PNG or WebP, resized for TikTok automatically), left out it follows the media; title (photo posts only, up to 90 characters); comments_enabled, duet_enabled, stitch_enabled (default off; Duet and Stitch are video only); disclose_your_brand, disclose_branded_content; ai_generated. linkedin (the member's own profile): media_type text | image | video, left out it follows the media; text (overrides caption, up to 3,000 characters); title (videos); visibility PUBLIC (default) | CONNECTIONS. Images: 1 to 20 JPEG, PNG or GIF (2 or more show as a gallery). Videos: one MP4, 3 seconds to 30 minutes, up to 500 MB. bluesky: media_type text | image | video, left out it follows the media; text (overrides caption, up to 300 characters); alt_text (one description per image, in order). Images: 1 to 4 JPEG, PNG, WebP or GIF (larger than 1 MB are resized to fit). Videos: one MP4, up to 3 minutes and 100 MB. Links, @mentions and #hashtags become clickable. x: media_type text | image | video, left out it follows the media; text (overrides caption, up to 280 characters; links count as 23, emoji as 2); alt_text (one description per image). Images: 1 to 4 JPEG, PNG or WebP up to 5 MB, or one GIF up to 15 MB. Videos: one MP4 or MOV, up to 2 min 20 s and 512 MB. X posts are billed separately: 2 cents each, 25 cents with a link.","additionalProperties":true}}}}},"required":["destinations"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/posts":{"post":{"operationId":"create_post","summary":"Create a post","description":"Creates a post for one or more accounts. It is sent only when you say when: scheduled_at publishes at that time, publish: \"now\" publishes right away. Without either (or with draft: true) it is only saved as a draft and nothing is posted; tell the person, and call publish_post when they want it sent. To schedule a series, call it once per post with each time. Unknown fields are refused. The result's next_step says what happens. If the post has problems it is saved as a draft and the problems are returned.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"caption":{"type":"string","maxLength":10000,"description":"The shared caption. Each platform's limit is checked (see list_social_accounts capabilities)."},"media_ids":{"type":"array","maxItems":35,"items":{"type":"string","format":"uuid"},"description":"Media to attach, in order (ids from list_media, import_media or an upload)."},"destinations":{"type":"array","minItems":1,"maxItems":20,"description":"Where the post goes: one entry per connected account (ids from list_social_accounts).","items":{"type":"object","required":["account_id"],"properties":{"account_id":{"type":"string","format":"uuid","description":"The connected account id."},"options":{"type":"object","description":"Per-platform settings. media_type is required for Instagram, Facebook and Threads. instagram: media_type image | reel | carousel; caption (overrides the shared caption). facebook: media_type text | link | image | reel | video; message (overrides caption); link (for link posts); title (videos). threads: media_type text | image | video | carousel; text (overrides caption). youtube (Shorts, one vertical video up to 3 min): title (required, up to 100 characters); description; privacy_status public | unlisted | private (required). Video covers (any platform's options): cover_media_id (an image from list_media) or cover_time_ms (a frame, in milliseconds from the start), not both. Instagram Reels and YouTube take either; TikTok direct posts take a frame only; LinkedIn videos take either; TikTok drafts, Facebook, Threads, Bluesky and X can't set a cover. Instagram crops the cover to a centred square in the profile grid. tiktok: delivery_mode direct (posts straight away; needs privacy_level PUBLIC_TO_EVERYONE | MUTUAL_FOLLOW_FRIENDS | FOLLOWER_OF_CREATOR | SELF_ONLY, the audience the person asked for, never assumed) or inbox (a draft in the creator's TikTok inbox, where they finish it and tap Post); left out, it follows the account's tiktok_delivery from list_accounts (direct unless the person chose inbox; set_tiktok_delivery changes it); media_type video (one video) | photo (1 to 35 images as a swipeable carousel; JPEG, PNG or WebP, resized for TikTok automatically), left out it follows the media; title (photo posts only, up to 90 characters); comments_enabled, duet_enabled, stitch_enabled (default off; Duet and Stitch are video only); disclose_your_brand, disclose_branded_content; ai_generated. linkedin (the member's own profile): media_type text | image | video, left out it follows the media; text (overrides caption, up to 3,000 characters); title (videos); visibility PUBLIC (default) | CONNECTIONS. Images: 1 to 20 JPEG, PNG or GIF (2 or more show as a gallery). Videos: one MP4, 3 seconds to 30 minutes, up to 500 MB. bluesky: media_type text | image | video, left out it follows the media; text (overrides caption, up to 300 characters); alt_text (one description per image, in order). Images: 1 to 4 JPEG, PNG, WebP or GIF (larger than 1 MB are resized to fit). Videos: one MP4, up to 3 minutes and 100 MB. Links, @mentions and #hashtags become clickable. x: media_type text | image | video, left out it follows the media; text (overrides caption, up to 280 characters; links count as 23, emoji as 2); alt_text (one description per image). Images: 1 to 4 JPEG, PNG or WebP up to 5 MB, or one GIF up to 15 MB. Videos: one MP4 or MOV, up to 2 min 20 s and 512 MB. X posts are billed separately: 2 cents each, 25 cents with a link.","additionalProperties":true}}}},"scheduled_at":{"type":"string","format":"date-time","description":"When to publish, in ISO 8601 with a timezone, e.g. 2026-10-05T15:00:00Z. Up to a year ahead. Without scheduled_at (or publish: \"now\") the post is only saved as a draft."},"publish":{"type":"string","enum":["now"],"description":"Set to \"now\" to publish right away. Only use it when the person asked for the post to go out now. Not with scheduled_at or draft."},"draft":{"type":"boolean","default":false,"description":"Only save a draft; do not send or schedule."}},"required":["destinations"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"list_posts","summary":"List posts","description":"Lists posts, newest first. Each post has published_at (when it first went out on any account; null until then), its destinations (one per account: status, live_url, published_at, error) and media (in order: id, media_type, mime_type, name, status, width, height, duration_seconds, url and thumbnail_url; url and thumbnail_url are temporary view links valid 1 hour, null until the item is ready; thumbnail_url is the image itself or a video's poster frame). Each account is named by its profile name (account), its platform and its handle (account_handle, without the @; null for LinkedIn, Facebook Pages and YouTube, which share no handle with Post Social), so two accounts with the same name can be told apart.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","awaiting_approval","approved","scheduled","processing","published","partially_published","failed","cancelled"]}},{"name":"platform","in":"query","required":false,"schema":{"type":"string","enum":["instagram","facebook","threads","youtube","tiktok","linkedin","bluesky","x"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25,"description":"How many to return (1 to 100)."}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"description":"How many to skip, for paging."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/posts/{post_id}":{"get":{"operationId":"get_post","summary":"Get a post","description":"Gets one post with its caption, published_at (when it first went out on any account; null until then), each account's status, live_url, published_at and errors, its approval state, media_ids and media (in order: id, media_type, mime_type, name, status, width, height, duration_seconds, url and thumbnail_url; url and thumbnail_url are temporary view links valid 1 hour, null until the item is ready; thumbnail_url is the image itself or a video's poster frame). Each account is named by its profile name (account), its platform and its handle (account_handle, without the @; null for LinkedIn, Facebook Pages and YouTube, which share no handle with Post Social), so two accounts with the same name can be told apart. If the post is publishing right now, waits up to about 25 seconds for the accounts still sending, so the live links are usually all there.","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The post id."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update_post","summary":"Edit a post","description":"Edits a draft, a post waiting for approval, or a scheduled post: its caption, time, media or accounts. Only the fields you pass change; media_ids and destinations each replace the whole list (to swap an image, upload the new one and pass its id in media_ids). To change a scheduled post, edit it here instead of creating a new post, which would publish twice. Editing an approved or scheduled post may send it back for approval.","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The post id."}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"caption":{"type":"string","maxLength":10000,"description":"The shared caption. Each platform's limit is checked (see list_social_accounts capabilities)."},"media_ids":{"type":"array","maxItems":35,"items":{"type":"string","format":"uuid"},"description":"Media to attach, in order (ids from list_media, import_media or an upload)."},"destinations":{"type":"array","minItems":1,"maxItems":20,"description":"Where the post goes: one entry per connected account (ids from list_social_accounts).","items":{"type":"object","required":["account_id"],"properties":{"account_id":{"type":"string","format":"uuid","description":"The connected account id."},"options":{"type":"object","description":"Per-platform settings. media_type is required for Instagram, Facebook and Threads. instagram: media_type image | reel | carousel; caption (overrides the shared caption). facebook: media_type text | link | image | reel | video; message (overrides caption); link (for link posts); title (videos). threads: media_type text | image | video | carousel; text (overrides caption). youtube (Shorts, one vertical video up to 3 min): title (required, up to 100 characters); description; privacy_status public | unlisted | private (required). Video covers (any platform's options): cover_media_id (an image from list_media) or cover_time_ms (a frame, in milliseconds from the start), not both. Instagram Reels and YouTube take either; TikTok direct posts take a frame only; LinkedIn videos take either; TikTok drafts, Facebook, Threads, Bluesky and X can't set a cover. Instagram crops the cover to a centred square in the profile grid. tiktok: delivery_mode direct (posts straight away; needs privacy_level PUBLIC_TO_EVERYONE | MUTUAL_FOLLOW_FRIENDS | FOLLOWER_OF_CREATOR | SELF_ONLY, the audience the person asked for, never assumed) or inbox (a draft in the creator's TikTok inbox, where they finish it and tap Post); left out, it follows the account's tiktok_delivery from list_accounts (direct unless the person chose inbox; set_tiktok_delivery changes it); media_type video (one video) | photo (1 to 35 images as a swipeable carousel; JPEG, PNG or WebP, resized for TikTok automatically), left out it follows the media; title (photo posts only, up to 90 characters); comments_enabled, duet_enabled, stitch_enabled (default off; Duet and Stitch are video only); disclose_your_brand, disclose_branded_content; ai_generated. linkedin (the member's own profile): media_type text | image | video, left out it follows the media; text (overrides caption, up to 3,000 characters); title (videos); visibility PUBLIC (default) | CONNECTIONS. Images: 1 to 20 JPEG, PNG or GIF (2 or more show as a gallery). Videos: one MP4, 3 seconds to 30 minutes, up to 500 MB. bluesky: media_type text | image | video, left out it follows the media; text (overrides caption, up to 300 characters); alt_text (one description per image, in order). Images: 1 to 4 JPEG, PNG, WebP or GIF (larger than 1 MB are resized to fit). Videos: one MP4, up to 3 minutes and 100 MB. Links, @mentions and #hashtags become clickable. x: media_type text | image | video, left out it follows the media; text (overrides caption, up to 280 characters; links count as 23, emoji as 2); alt_text (one description per image). Images: 1 to 4 JPEG, PNG or WebP up to 5 MB, or one GIF up to 15 MB. Videos: one MP4 or MOV, up to 2 min 20 s and 512 MB. X posts are billed separately: 2 cents each, 25 cents with a link.","additionalProperties":true}}}},"scheduled_at":{"type":"string","format":"date-time","description":"When to publish, in ISO 8601 with a timezone, e.g. 2026-10-05T15:00:00Z. Up to a year ahead."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete_post","summary":"Delete a draft","description":"Permanently deletes a draft or a cancelled post. Sent posts are kept for the record.","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The post id."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/posts/{post_id}/publish":{"post":{"operationId":"publish_post","summary":"Send a draft","description":"Publishes a draft now, or schedules it with scheduled_at.","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The post id."}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"scheduled_at":{"type":"string","format":"date-time","description":"When to publish, in ISO 8601 with a timezone, e.g. 2026-10-05T15:00:00Z. Up to a year ahead. Leave out to publish now."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/posts/{post_id}/reschedule":{"post":{"operationId":"reschedule_post","summary":"Move a scheduled post","description":"Changes the time of a scheduled post that has not started sending.","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The post id."}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scheduled_at":{"type":"string","format":"date-time","description":"The new time, ISO 8601 with a timezone."}},"required":["scheduled_at"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/posts/{post_id}/cancel":{"post":{"operationId":"cancel_post","summary":"Cancel a post","description":"Cancels a draft, a post waiting for approval, or a scheduled post before it starts sending. Posts already sent cannot be cancelled.","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The post id."}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/usage":{"get":{"operationId":"get_usage","summary":"Usage and plan limits","description":"Shows usage for a period: posts published (by account and by who made them), posts created by AI, failed publishes, API calls per connection, a day-by-day series, and how close the workspace is to each plan limit.","parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","enum":["this_month","last_month","last_7_days","last_30_days","today"],"default":"this_month","description":"Which period, in UTC."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/analytics":{"get":{"operationId":"list_analytics","summary":"Post stats","description":"Lists published posts with their stats (views, likes, comments, shares, and saves, reposts or quotes where the platform reports them), plus totals per platform. Sort by any stat to find what performed best.","parameters":[{"name":"platform","in":"query","required":false,"schema":{"type":"string","enum":["instagram","facebook","threads","youtube","tiktok","linkedin","bluesky","x"]}},{"name":"period","in":"query","required":false,"schema":{"type":"string","enum":["last_7_days","last_30_days","last_90_days","all"],"default":"last_30_days","description":"Posts published in this period."}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["published_at","views","likes","comments","shares","saves","reposts","quotes"],"default":"published_at"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25,"description":"How many to return (1 to 100)."}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"description":"How many to skip, for paging."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/posts/{post_id}/analytics":{"get":{"operationId":"get_post_analytics","summary":"One post's stats over time","description":"Shows one post's stats on each platform it went to, with a day-by-day history.","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"The post id."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/analytics/refresh":{"post":{"operationId":"refresh_analytics","summary":"Refresh stats now","description":"Fetches fresh stats from the platforms for one post (or all recent posts) instead of waiting for the next automatic update. At most every 30 minutes per post.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Repeat-safe writes: the same key within 24 hours returns the first result."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"post_id":{"type":"string","format":"uuid","description":"The post id."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/results":{"get":{"operationId":"list_post_results","summary":"Publishing results","description":"Shows each account's outcome: published (with the live link), failed (with the platform's reason in plain language), or still in progress. One post's results (waiting up to about 25 seconds for accounts still sending), or the most recent overall. Each account is named by its profile name (account), its platform and its handle (account_handle, without the @; null for LinkedIn, Facebook Pages and YouTube, which share no handle with Post Social), so two accounts with the same name can be told apart.","parameters":[{"name":"post_id","in":"query","required":false,"schema":{"type":"string","format":"uuid","description":"The post id."}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25,"description":"How many to return (1 to 100)."}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"description":"How many to skip, for paging."}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}