POST /v1/posts, name each destination in targets, and read the delivery result per platform. The same request publishes to Instagram, Facebook, Threads, TikTok, YouTube, LinkedIn, X, Pinterest, Google Business Profile, Zalo, and Bluesky.
Zalo publishes text articles only, and Bluesky publishes text and up to 4 images. What each platform accepts is on its publishing page, linked from Platform support. A post costs 1 post credit when it is published or scheduled, whatever the number of targets. Saving a draft is free. See Plans and usage.
Endpoints
GET /v1/posts takes these query parameters, all optional. Pagination is in Pagination.
Your first post
Publish one text post to a Threads account and a Facebook Page:post.published, post.partial, or post.failed event. Track post delivery explains the statuses, the HTTP codes, and the per-target errors.
Choose what the request does
A
scheduled_at that is not RFC 3339 returns 400 validation.field_invalid. With at least one target, a scheduled_at in the past returns 400 validation.field_invalid. With no targets, the post is saved as a draft.
Fields every platform shares
Each item of
targets has:
A target that does not set a field inherits the post-level value. The same account and Page twice in one request returns
400 publishing.duplicate_target.
SocialAPI ignores request fields it does not recognize. POST /v1/posts is not idempotent: a request returns 409 resource.conflict when the same account has a post with the same title and text that was created in the last 24 hours and is now published. Send skip_duplicate_check: true to post the same content again. See IDs.
Where platform data goes
Put platform-specific fields inplatform_data, in one of 2 places:
- On the post, under the platform slug:
platform_data.instagramapplies to every Instagram target. - On a target, as flat keys:
targets[].platform_dataapplies to that target only.
platform_data is dropped, so write {"reply_control": "everyone"} on a target, not {"threads": {...}}.
Validate before you publish
POST /v1/posts/validate checks a post without creating it and costs no credit. Name the platforms with platforms, account_ids, or targets. It accepts the same text, media, scheduled_at, segments, and platform_data fields as POST /v1/posts.
errors lists the violations that block publishing. warnings lists the rest, for example text within 10% of a platform’s length limit. Each issue has platform, field, message, and, when it applies to one target, target and segment_index. A scheduled_at that is not in the future returns 400 validation.field_invalid. Calling it without any platform returns 400 validation.field_required.
POST /v1/posts runs the same validation unless you send skip_validation: true. A post with errors returns 400 publishing.validation_failed, and error.meta.issues holds the same list. Drafts skip validation until you publish them.
GET /v1/posts/validate returns each platform’s text and media rules, so a client can check content before it sends a request. Add ?platform=instagram for one platform. An unknown platform returns 400 validation.field_invalid.
counting_mode is chars, utf16, or bytes. TikTok’s per-creator privacy levels and maximum duration come from GET /v1/accounts/{id}/creator-info. See TikTok publishing.
supports_mixed_media is about platform_attachment_id items only: false means a platform_attachment_id item must be the only item in media and cannot sit next to url or media_id items. It does not limit a carousel, which can mix images and videos where carousel.allow_video is true and carousel.images_only is false, as on Instagram.
Drafts
A post saved withoutpublish_now or scheduled_at is a draft. It costs no credit, skips validation, and can have no targets.
"status": "draft". Publish it later with POST /v1/posts/{pid}/publish, which costs 1 post credit.
Edit a post before it publishes
PATCH /v1/posts/{pid} changes a post that has not been delivered yet. Send only the fields you change.
- Editable statuses:
draft,scheduled, andfailed. Any other status returns409 post.state_invalid, anderror.meta.stateis the current status. Apublishedpost cannot be edited on any platform, except a YouTube video, which has its own endpoint. See YouTube publishing. - Editable fields:
text,title,media,visibility,first_comment,scheduled_at,hidden,segments,platform_data, andtargets. Sendingtargetsreplaces all the post’s targets. - Validation: an edit to content runs the validation again. It returns
400 publishing.validation_failedfor a post with errors, unless you sendskip_validation: true. - After a failure: edit a
failedpost, then callPOST /v1/posts/{pid}/retry. - Event: SocialAPI sends
post.updatedwithchanged_fields.
Import posts from a CSV file
POST /v1/posts/import creates posts from a CSV file sent as multipart form data in the file field. The first row is a header and must start with the text column. The file holds at most 500 data rows and 5 MB.
Columns are read by position, in this order. A row can end early: only
text is required. There is no publish_now column, so an imported post is a draft or a scheduled post.
dry_run=true checks only each row’s text, scheduled_at, and platform_data format, and creates nothing. Row numbers count the header as row 1, so the first data row is row 2. A request with no file field returns 400 validation.field_required. A file over 5 MB, a file that is not valid CSV, a header that does not start with text, a file with no data rows, or a file with more than 500 data rows returns 400 validation.field_invalid. When the file has more valid rows than you have post credits left, drafts included, the request returns 429 billing.post_limit and creates nothing. Each created post is a normal post: it costs 1 post credit when scheduled and nothing as a draft. Without dry_run, a row that passes these checks but fails when SocialAPI creates the post (for example 409 resource.conflict or 400 publishing.validation_failed) is listed in errors with its row number and message and counted in failed. The other rows are still created, and the response is still 200: check created, failed, and errors.
Unpublish and delete
POST /v1/posts/{pid}/unpublish removes the published copies from the platforms and keeps the post. It returns 200 {"success": true} when every targeted copy was removed, 400 validation.field_invalid when no target is published with a platform_post_id, 501 resource.not_supported when at least one remaining target is on a platform that cannot delete posts (such as TikTok), and 500 system.internal when a platform fails to delete a copy. With a 501 or 500, every target that was removed is unpublished. See Unpublish a post.
DELETE /v1/posts/{pid} deletes a post and also removes its published copies. See Delete a post.
Platform support
Each platform accepts different content, sizes, andplatform_data keys. The cross-platform matrix is on Platform support. The details are on each platform’s publishing page:
- Threads
- TikTok
- YouTube
- X / Twitter
- Google Business Profile
- Zalo
- Bluesky
scheduled_at. A platform that cannot do something returns the error described on its page, for example 501 resource.not_supported.
Limits
Errors
400 publishing.validation_failed, 409 resource.conflict, 409 post.state_invalid, and 429 publishing.velocity_limit are the codes you branch on most. Every code is in Errors. A failure after the request returned appears on the target, not as an HTTP error. A target on an account without the posts.write permission fails with account.permission_missing. See Track post delivery.
Scopes
GET /v1/posts/validate needs no scope: any valid API key can call it.
A brand-restricted key can publish only to accounts in its brands. See Scoped API keys.
Events
Payloads are in Webhook events.
Use from an AI agent
The import endpoint has no tool. See MCP tools.
Related
- Track post delivery: statuses, HTTP codes, target errors, retry, and delete
- Schedule posts: publish at a chosen time
- Upload media: upload files and attach media
- Platform support: what each platform publishes
- Errors: every error code with its HTTP status