1. Overview
X posts publish through your own X app, so X bills each write to your X developer account. A post carries text and up to 4 images, 1 video, or 1 GIF. A thread chains text-only posts after the first one.2. Supported media
A post holds up to 4 images, or exactly one video, or exactly one GIF, never a mix. More than 4 items, a file type X does not accept, or a file too large to upload returns
400 publishing.validation_failed. With skip_validation: true, the target fails at publish time with 400 validation.media_count, 400 validation.media_type, or 400 validation.media_size.
File limits for media you upload to SocialAPI (source_type: "media_id"): the limits in the table apply, and a file outside them fails validation with 400 publishing.validation_failed. SocialAPI uploads the media to X before it creates the post. If an upload fails, the whole post fails: SocialAPI never publishes the text without its media.
3. Create post
UsePOST /v1/posts with targets pointing at an X account and publish_now: true. The text field becomes the post text.
Platform data fields
Send these keys inplatform_data.twitter on the post, which applies them to every X target. To set them for one target, send them flat in that target’s platform_data (targets[].platform_data, with no twitter key). When both set the same key, the target’s value wins. All keys are optional.
SocialAPI forwards any additional keys in
platform_data.twitter that the table above lists, and no others. X has no other publish options that SocialAPI passes through: an unlisted key is not sent, and validation returns a warning naming the key. A value of the wrong type, an unlisted reply_settings value, or a thread entry that is empty or longer than 280 characters fails validation on that field, and POST /v1/posts/validate reports it before anything is published. auto_populate_reply_metadata or exclude_reply_user_ids without in_reply_to_tweet_id returns a warning, because it has no effect.
Threads
Setsegments to publish a thread. The post’s text is the first post, and each segment is posted as a reply to the one before it. A thread holds up to 25 continuation posts, each up to 280 characters.
platform_data.twitter settings apply to the first post only. The target’s platform_post_id is the first post’s ID. Each post in a thread is a separate X write that X bills to you.
Reply to a post
Setplatform_data.twitter.in_reply_to_tweet_id to publish a reply:
Scheduling
Setscheduled_at to an ISO 8601 timestamp (UTC) instead of publish_now. SocialAPI holds the post and publishes it at that time.
4. Update post
SocialAPI offers no edit of a published X post.PATCH /v1/posts/{pid} edits only a post in draft, scheduled, or failed status, and returns 409 post.state_invalid for a published post. To correct a published post, delete it and publish a new one.
5. Delete post
DELETE /v1/posts/{pid} deletes the post from X when its X target is published. For a thread it deletes the first post only: the continuation posts stay on X until you delete them there. See Delete a post.
6. Retrieve posts
GET /v1/posts?platform=twitter&account_ids={id} lists the account’s posts and replies, without reposts. Each published target carries platform_post_id, permalink, and metrics with likes and comments (the reply count). GET /v1/posts/{pid}/metrics reads the current counts from X. X reports no other counts through SocialAPI.
7. Quirks, errors, and recovery
Text limit
Text holds 280 characters, counted as Unicode characters. A longer text fails with400 publishing.validation_failed, and the issue names the field text.
A thread can stop partway
If a continuation post fails, publishing stops and the target fails. The posts already published stay on X, because X has no batch rollback. Check the account’s X timeline before you retry:POST /v1/posts/{pid}/retry publishes the whole thread again.
Media must finish processing
X processes a video or GIF after upload. SocialAPI waits for X to finish before it creates the post, and a post with a large video takes longer to reachpublished.
X bills links at a higher rate
X charges more for a post that contains a URL than for a post without one. The charge is on your X developer account, not on SocialAPI.Errors on a failed target
A publish that fails after the request returned puts a structured error on the target, intargets[].error, with its code. Every code is in Errors.
401 platform.twitter.auth: the access token is no longer valid and renewal failed. The account is markedtoken_expired, and every later call that acts on the account returns403 account.reconnection_requireduntil you reconnect it.403 platform.twitter.insufficient_scope: the account’s access lacks a scope, for examplemedia.write. Reconnect the account.402 platform.twitter.usage_capped: your X app is out of credits or reached its spending limit. Add credits or raise the limit in the X Developer Console, then retry.429 platform.twitter.rate_limit: X’s own limit. Wait, then retry.
Recovery
- Rate limit. Wait for the seconds in
Retry-After, or at least 60 seconds when it is absent, then sendPOST /v1/posts/{pid}/retry. See Delivery. - Auth or scope error. Reconnect the X account through the OAuth flow.
- Usage cap. Add X credits, then send
POST /v1/posts/{pid}/retry.
8. Full worked example
This example publishes a post with an image and a 2-post thread, then reads the result. Step 1: Upload mediaupload_url, then call POST /v1/media/{media_id}/verify. The steps and the response are in Media. The result is a media_id, here f47ac10b-58cc-4372-a567-0e02b2c3d479.
Step 2: Publish the post
status: "publishing", as in Create post.
Step 3: Check the status
9. Required OAuth scopes
SocialAPI requests these scopes when the person connects the account, through your own X app. The full list is in X scopes.
The scopes a scoped API key needs, the credit cost of a post, and the MCP tools that publish are on Publishing.