X (Twitter) API Posting: Integration Guide

Understanding OAuth authentication, the chunked media upload flow, and API endpoints for posting to X.

X (Twitter) API Posting: Integration Guide
Quick answer

Posting to X via API requires OAuth 2.0 with PKCE (or legacy OAuth 1.0a) and tweet.write scope. POST to api.x.com/2/tweets with text up to 280 characters. Media uses a three-step chunked upload: INIT, APPEND segments under 5 MB each, then FINALIZE, polling for processing on videos and GIFs before attaching the media_id to the post.

How do you get X (Twitter) API developer access?

Before your app can post to X on behalf of users, you need access through the X Developer Portal:

  1. Developer Account — Apply at developer.x.com. You’ll need to describe your intended use case
  2. Project & App — Create a project and app in the Developer Portal to get your API keys
  3. Access Level — As of February 2026, new developers are placed on pay-per-use by default ($0.01 per post, $0.005 per read). The legacy Basic ($200/month) and Pro ($5,000/month) tiers remain only for existing subscribers. See the full breakdown in X API pricing in 2026.

Pay-per-use removes monthly caps but introduces a credit balance to manage. Rate limits still apply at every tier.

How does X API authentication work?

X supports two authentication methods for posting:

  • OAuth 1.0a — Three-legged flow using consumer key, consumer secret, access token, and access token secret. The request must include a computed oauth_signature in the Authorization header
  • OAuth 2.0 with PKCE — User authorization flow that returns user access tokens. Requires the tweet.read, tweet.write, users.read, and offline.access scopes

For posting on behalf of users, both require user-level authentication — app-only Bearer tokens cannot create posts.

OAuth 2.0 with PKCE is the recommended approach. The flow:

  1. Direct the user to https://x.com/i/oauth2/authorize with your client_id, redirect_uri, scope, and a PKCE code_challenge
  2. User authorizes your app and is redirected back with an authorization code
  3. Exchange the code for an access token via POST to https://api.x.com/2/oauth2/token
  4. Use the access token in the Authorization: Bearer header for subsequent requests

Access tokens expire after 2 hours. Use the refresh_token to get a new one.

How do you create a post on X via API?

POST to https://api.x.com/2/tweets:

{
"text": "Your post text here"
}

Returns:

{
"data": {
"id": "1234567890",
"text": "Your post text here"
}
}

The text field supports up to 280 characters on the free and basic tiers, or up to 25,000 characters on the Pro tier.

How do you post to X with media?

Media must be uploaded separately before attaching it to a post. The flow is: upload the media, get a media_id, then reference it when creating the post.

POST to https://api.x.com/2/tweets:

{
"text": "Your caption here",
"media": {
"media_ids": ["<MEDIA_ID>"]
}
}

You can attach up to 4 images, 1 video, or 1 animated GIF per post.

How does the X chunked media upload flow work?

All media uploads use a three-step chunked upload process: INIT, APPEND, FINALIZE.

File size limits

  • Images: 5 MB (JPEG, PNG, WebP)
  • Animated GIFs: 15 MB
  • Videos: 512 MB (MP4, MOV)

Step 1: Initialize

POST to https://api.x.com/2/media/upload/initialize:

{
"media_type": "video/mp4",
"total_bytes": 12345678,
"media_category": "tweet_video"
}

The media_category values for posts are:

  • tweet_image — Images
  • tweet_gif — Animated GIFs
  • tweet_video — Videos

Returns:

{
"data": {
"id": "<MEDIA_ID>",
"expires_after_secs": 86400
}
}

Step 2: Append

POST to https://api.x.com/2/media/upload/<MEDIA_ID>/append:

Upload the file in chunks using multipart/form-data with:

  • media — The binary chunk data
  • segment_index — Integer starting at 0, incremented for each chunk

Chunk size should be no more than 5 MB per request. For a 12 MB video, you would send three APPEND requests with segment_index values 0, 1, and 2.

Step 3: Finalize

POST to https://api.x.com/2/media/upload/<MEDIA_ID>/finalize

Returns:

{
"data": {
"id": "<MEDIA_ID>",
"processing_info": {
"state": "pending",
"check_after_secs": 5
}
}
}

How do you check X media upload status?

Videos and GIFs require server-side processing after finalization. If processing_info is present in the FINALIZE response, you must poll for completion before attaching the media to a post.

GET https://api.x.com/2/media/upload/<MEDIA_ID>:

{
"data": {
"processing_info": {
"state": "in_progress",
"progress_percent": 45,
"check_after_secs": 10
}
}
}

Processing states:

  • pending — Queued for processing
  • in_progress — Currently processing, check progress_percent
  • succeeded — Ready to attach to a post
  • failed — Processing failed, upload must be retried

Always wait check_after_secs before polling again.

How do you create replies and quote posts on X?

To create a reply, include the reply object:

{
"text": "Your reply",
"reply": {
"in_reply_to_tweet_id": "<TWEET_ID>"
}
}

To create a quote post, include quote_tweet_id:

{
"text": "Your commentary",
"quote_tweet_id": "<TWEET_ID>"
}

How do you create polls on X via API?

Posts can include a poll:

{
"text": "Which do you prefer?",
"poll": {
"options": ["Option A", "Option B", "Option C"],
"duration_minutes": 1440
}
}

Duration must be between 5 and 10,080 minutes (7 days).

The same video, through Postproxy

Here’s how it’s done with Postproxy. One simple request with only what matters:

Terminal window
curl -X POST "https://api.postproxy.dev/api/posts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"post": {
"body": "3 tips that changed how we approach customer onboarding"
},
"profiles": ["twitter"],
"media": ["https://example.com/video.mp4"]
}'

One request. Postproxy handles the chunked upload, the processing status polling, and the post creation.

What Postproxy handles

Postproxy manages OAuth tokens and handles the complexity:

  • OAuth 2.0 token exchange and automatic refresh
  • The three-step chunked media upload (INIT, APPEND, FINALIZE)
  • Processing status polling for videos and GIFs
  • Unified API for text posts, images, videos, and GIFs
  • Rate limit monitoring and request pacing

Your system sends content. Postproxy handles the X-specific implementation.

Connect your X account and start publishing through the Postproxy API.

Ready to get started?

Start with our free plan and scale as your needs grow. No credit card required.