Guides · Updated

Threads, polls and media with the X API v2

All three go through one endpoint, POST /2/tweets, with a token for the account that posts. A thread is a chain of replies, a poll is a field on the post, and an image or video is uploaded first and attached by its ID.

Each request here was checked on October 26, 2026 against X's published OpenAPI spec (version 2.172) and its docs, linked under Sources. None was sent: real calls cost money. Prices are from X's pricing page, checked the same day. X can change both, so check them before you build on them.

Before you start

A thread: each part replies to the one before

X's API has no thread call. Post the first part, keep the id X returns, then post each next part as a reply to the part before it.

# Part 1
curl -X POST "https://api.x.com/2/tweets" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "We shipped weekly charts today. Here is what changed."}'
# {"data": {"id": "1880000000000000001", "text": "We shipped weekly charts today. …"}}

# Part 2, a reply to part 1
curl -X POST "https://api.x.com/2/tweets" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "Charts now cover any week you pick.",
       "reply": {"in_reply_to_tweet_id": "1880000000000000001"}}'

# Part 3 replies to part 2's id, and so on.

A poll: one field on the post

curl -X POST "https://api.x.com/2/tweets" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "What should we build next?",
       "poll": {"options": ["Better charts", "More exports"], "duration_minutes": 1440},
       "reply_settings": "following"}'
curl -G "https://api.x.com/2/tweets" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -d ids=1880000000000000003 -d expansions=attachments.poll_ids \
  -d poll.fields=options,duration_minutes

An image or video: upload, then attach

Upload the file first, then put the id X returns in media.media_ids. A post takes up to 4 photos, 1 animated GIF or 1 video.

Video, and any large file, goes through X's chunked upload: start it, send the file in pieces, finish it, and wait while X processes it.

# 1. Start: the file's type, its exact size in bytes, and what it's for
curl -X POST "https://api.x.com/2/media/upload/initialize" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"media_type": "video/mp4", "total_bytes": 10485760,
       "media_category": "tweet_video"}'
# {"data": {"id": "1880028106020515840", "expires_after_secs": 86400, …}}

# 2. Send pieces of 5 MB or less (4 MB here), numbered from 0
split -b 4194304 clip.mp4 part_
curl -X POST "https://api.x.com/2/media/upload/1880028106020515840/append" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -F "segment_index=0" -F "media=@part_aa"
# Then segment_index=1 with part_ab, and so on.

# 3. Finish. If the answer has processing_info, check until it says succeeded
curl -X POST "https://api.x.com/2/media/upload/1880028106020515840/finalize" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN"
curl -G "https://api.x.com/2/media/upload" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -d command=STATUS -d media_id=1880028106020515840

# 4. Post it
curl -X POST "https://api.x.com/2/tweets" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "Weekly charts, in 40 seconds.",
       "media": {"media_ids": ["1880028106020515840"]}}'

A small image can go up in one request instead. Give it alt text before you post it:

curl -X POST "https://api.x.com/2/media/upload" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -F "media=@chart.png" -F "media_category=tweet_image"
# {"data": {"id": "1880028106020515841", …}}

curl -X POST "https://api.x.com/2/media/metadata" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id": "1880028106020515841",
       "metadata": {"alt_text": {"text": "Bar chart of weekly views, up 40%"}}}'

What each call costs

Call X bills it as Price
POST /2/tweets: a post, a thread part or a poll Post: Create $0.015 a request
POST /2/tweets with a link in the text Post: Create (with URL) $0.200 a request
/2/media/upload: one-step, initialize, append, finalize, status Not in X's table Not listed
POST /2/media/metadata: alt text Media Metadata $0.005 a request
GET /2/tweets: reading posts back Posts: Read $0.005 a post

A three-part thread with an image and alt text on part 1, a link in part 3, and one read-back of all three parts costs $0.250, plus whatever X charges for the upload: $0.030 for parts 1 and 2, $0.200 for part 3, $0.005 for the alt text and $0.015 to read the three back. X charges a read of the same post once per UTC day, which it calls a soft guarantee. X API pricing, explained has monthly totals.

What Flaghand adds

Flaghand is a publishing desk where your agent drafts and the owner approves. The agent sends Flaghand the content, not the X calls: a thread is one draft of 2 to 25 posts, a poll is the same poll object, and media is uploaded to Flaghand once, with its alt text.

curl -X POST "https://app.flaghand.com/api/v1/workspaces/$WS/drafts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"kind": "x_thread", "connection_id": "con_…",
       "post_at": "2026-11-02T16:30:00.000Z", "body": {"posts": [
         {"text": "What should we build next?", "poll": {
           "options": ["Better charts", "More exports"], "duration_minutes": 1440}},
         {"text": "Here is the chart we have now.", "media": [
           {"media_id": "med_…", "alt": "Bar chart of weekly views"}]}]}}'

The agent then checks the draft and asks the owner to approve it. From there:

Where it does less: files up to 50 MB, so no long videos; X and LinkedIn personal profiles only; and it's in early access, so sign-up isn't open yet. Every field is in Flaghand's drafts and media docs and agent documentation. Get access to Flaghand.

Sources

All read on October 26, 2026. No X API call was made for this guide.