X (Twitter) Upload API
This section details how to post text, images and videos to X using the API.
Base URL
https://api.multi-upload-tool.com/api/v1
Authentication
All requests require an API Token in the header:
x-api-key: YOUR_API_TOKEN
X credits
X charges for every post published through its API, so X posts are billed in credits:
| Post | Credits |
|---|---|
| Text, image or video post | 1 |
| Post whose text contains a link | 15 |
A link is detected with X’s own rules (the official twitter-text library): a URL (https://…) or a bare domain with any real top-level domain, such as example.com, youtu.be/… or site.museum. A missing space after a period can make one: sentence.Next is a link, because .next is a top-level domain. Node.js is not.
Each plan includes a monthly allowance (Basic 50, Pro 150, Scale 300), reset on the 1st of each month (UTC). Beyond it, credits come from packs bought on the Plans page; purchased credits never expire. The credits are debited when the post is published, once per account, and given back if publishing fails. A refunded pack takes back its share of credits. A post published with no credits left fails with Not enough X credits. Retry it once credits are added.
Check the balance
GET /x-credits (scope posts:read) returns the balance, the cost of each kind of post and the packs on sale. includedRemaining is what is left of the monthly allowance, reset each month (UTC); purchased credits never expire. An X post fails when total is short of its cost.
curl https://api.multi-upload-tool.com/api/v1/x-credits \
-H "x-api-key: YOUR_API_TOKEN"{
"success": true,
"data": {
"balance": { "monthlyAllowance": 150, "includedRemaining": 112, "purchased": 40, "total": 152 },
"costs": { "post": 1, "linkPost": 15 },
"packs": [
{ "name": "X Credits 250", "credits": 250, "price": 9 },
{ "name": "X Credits 1000", "credits": 1000, "price": 25 },
{ "name": "X Credits 5000", "credits": 5000, "price": 99 }
]
}
}Credits of a post
GET /upload/:id on an X post carries xCredits: { spent, refunded, net }. It is null until the job has run. A failed post is refunded; a retry debits again.
Validation & Limits
| Constraint | Limit | Notes |
|---|---|---|
| Post text | 280 characters (25,000 with x_premium) | Counted as X counts them: a link always counts 23, emoji and CJK characters count 2. |
| Media per post | 1 video or up to 4 images | Text-only posts are allowed |
| Image file size | 5 MB per image, 15 MB per GIF | JPG, PNG, WEBP, GIF. A GIF must be posted on its own, without other images. |
| Video file size | 1 GB | MP4, MOV, WebM. Files over 100 MB must use presigned uploads (x_videos). |
| Video duration | 20 minutes (125 with x_premium) | |
| Daily posts | 50 per account |
Source: X API — Media upload
Duplicate content
X’s developer policy bans posting identical content across several accounts. An X post is refused when, in the last 30 days, another X account of the same owner already published:
- the same text: case, spaces, punctuation and emoji are ignored. Only captions of at least 20 letters or digits are compared.
- the exact same media file (SHA-256). A re-encoded video is a different file and is not caught.
The job fails with a clear error and the X credits are given back. A bulk post may target only one X account: the other X accounts of the batch get a per-account error.
Upload a post
Endpoint
POST /upload
Content-Type
multipart/form-data
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
accountId | string | Yes | The ID of the connected X account. |
title | string | Yes* | The post text. Maximum 280 characters. *Optional when media is attached. |
description | string | No | Alternative to title for the post text. |
photo | File / File[] / URL | No | Up to 4 images. |
video | File / URL | No | One video. |
x_reply_settings | string | No | Who can reply: everyone (default), following, mentionedUsers, subscribers, verified. |
x_made_with_ai | boolean | No | Label the post as made with AI. |
x_paid_partnership | boolean | No | Label the post as a paid partnership. |
x_alt_text | string | No | Images only. Alt text applied to every image. Maximum 1000 characters. |
x_tagged_users | string | No | Images only. Up to 10 X usernames, as a JSON array or comma-separated. A tag only shows if the person allows photo tagging. |
x_subtitles_url | string | No | Video only. URL of an SRT or VTT file (max 1 MB) shown as captions. |
x_subtitles_language | string | No | 2-letter language code of the subtitles. Default EN. Needs x_subtitles_url. |
x_poll_options | string | No | Text-only posts (X refuses a poll with media). 2 to 4 choices of max 25 characters, as a JSON array or comma-separated. |
x_poll_duration_minutes | number | No | 5 to 10080. Default 1440. Needs x_poll_options. |
x_community_id | string | No | Numeric id of an X Community to post to. |
x_share_with_followers | boolean | No | With x_community_id: also show the post to followers. |
x_super_followers_only | boolean | No | Only Super Followers see the post. The account needs X subscriptions enabled. |
x_premium | boolean | No | The account has X Premium: text up to 25,000 characters, videos up to 125 minutes. |
Booleans are sent as true / false. An unknown x_* field is refused (400) with x_<field>: <message>.
Example Request
curl -X POST https://api.multi-upload-tool.com/api/v1/upload \
-H "x-api-key: YOUR_API_TOKEN" \
-F "accountId=cmf3k9x2a0001l408q7vbn2hd" \
-F "title=New feature out today" \
-F "video=@clip.mp4"Text post with a poll
curl -X POST https://api.multi-upload-tool.com/api/v1/upload \
-H "x-api-key: YOUR_API_TOKEN" \
-F "accountId=cmf3k9x2a0001l408q7vbn2hd" \
-F "title=Which feature should we ship next?" \
-F "x_poll_options=Calendar,Analytics,Both" \
-F "x_poll_duration_minutes=1440" \
-F "x_reply_settings=following"Photo post with alt text
curl -X POST https://api.multi-upload-tool.com/api/v1/upload \
-H "x-api-key: YOUR_API_TOKEN" \
-F "accountId=cmf3k9x2a0001l408q7vbn2hd" \
-F "title=Sunset from the office" \
-F "photo=@sunset.jpg" \
-F "x_alt_text=A warm golden sunset over the ocean" \
-F "x_tagged_users=@jane"Video post with subtitles
curl -X POST https://api.multi-upload-tool.com/api/v1/upload \
-H "x-api-key: YOUR_API_TOKEN" \
-F "accountId=cmf3k9x2a0001l408q7vbn2hd" \
-F "title=New feature out today" \
-F "video=@clip.mp4" \
-F "x_subtitles_url=https://example.com/clip.srt" \
-F "x_subtitles_language=en" \
-F "x_made_with_ai=true"Example Response
{
"success": true,
"message": "Upload queued successfully",
"data": {
"uploadId": 789,
"status": "pending",
"jobId": "job_456"
}
}Analytics
X accounts are synced once a day, at night. Because X bills every read, only the posts published through Multi Upload Tool in the last 29 days are read, plus the follower count. Posts published directly on X are not tracked. Views are X impressions, comments are replies, shares are reposts + quotes. See Analytics.