Skip to main content

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.
Attach media through the media array. Each item sets a source_type: media_id for a file you uploaded to SocialAPI, url for a public http(s) URL SocialAPI fetches at publish time, or platform_attachment_id for a media ID you created through X yourself. To upload first, see Media. Uploading needs the media.write scope: an account connected without it fails with 403 platform.twitter.insufficient_scope until the person reconnects.

3. Create post

Use POST /v1/posts with targets pointing at an X account and publish_now: true. The text field becomes the post text.
Response (HTTP 201):

Platform data fields

Send these keys in platform_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

Set segments 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.
Continuation posts are text only: media on a segment is dropped, with a validation warning. The 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

Set platform_data.twitter.in_reply_to_tweet_id to publish a reply:

Scheduling

Set scheduled_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 with 400 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 reach published. 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, in targets[].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 marked token_expired, and every later call that acts on the account returns 403 account.reconnection_required until you reconnect it.
  • 403 platform.twitter.insufficient_scope: the account’s access lacks a scope, for example media.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 send POST /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 media
Upload the file to the returned upload_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
Response (HTTP 201): the post with status: "publishing", as in Create post. Step 3: Check the status
Response (HTTP 200) once published:

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.
Last modified on October 13, 2026