Twitter/X API
Schedule and automate Twitter/X posts with Late API - Tweets, threads, images, videos, and GIFs
Quick Reference
| Property | Value |
|---|---|
| Character limit | 280 (free) / 25,000 (Premium) |
| Images per post | 4 (or 1 GIF) |
| Videos per post | 1 |
| Image formats | JPEG, PNG, WebP, GIF |
| Image max size | 5 MB (images), 15 MB (GIFs) |
| Video formats | MP4, MOV |
| Video max size | 512 MB |
| Video max duration | 140 seconds |
| Threads | Yes (via threadItems) |
| Scheduling | Yes |
| Inbox (DMs) | Yes (add-on) |
| Inbox (Comments) | Yes (add-on) |
| Analytics | Yes |
Before You Start
Twitter has a strict 280 character limit for free accounts. URLs always count as 23 characters regardless of actual length. Emojis count as 2 characters. If you're cross-posting from platforms with higher limits (LinkedIn 3,000, Facebook 63,000), use customContent to provide a shorter Twitter version or your post WILL fail.
Additional requirements:
- Duplicate tweets are rejected (even very similar content)
- Free accounts: 280 characters, Premium accounts: 25,000 characters
Quick Start
Post a tweet in under 60 seconds:
const { post } = await late.posts.createPost({
content: 'Hello from Late API!',
platforms: [
{ platform: 'twitter', accountId: 'YOUR_ACCOUNT_ID' }
],
publishNow: true
});
console.log('Tweet posted!', post._id);result = client.posts.create(
content="Hello from Late API!",
platforms=[
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
publish_now=True
)
post = result.post
print(f"Tweet posted! {post['_id']}")curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello from Late API!",
"platforms": [
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
"publishNow": true
}'Content Types
Text Tweet
A simple text-only tweet. Keep it under 280 characters for free accounts.
const { post } = await late.posts.createPost({
content: 'Just shipped a new feature. Check it out!',
platforms: [
{ platform: 'twitter', accountId: 'YOUR_ACCOUNT_ID' }
],
publishNow: true
});
console.log('Tweet posted!', post._id);result = client.posts.create(
content="Just shipped a new feature. Check it out!",
platforms=[
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
publish_now=True
)
post = result.post
print(f"Tweet posted! {post['_id']}")curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Just shipped a new feature. Check it out!",
"platforms": [
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
"publishNow": true
}'Tweet with Image
Attach up to 4 images per tweet. JPEG, PNG, WebP, and GIF formats are supported.
const { post } = await late.posts.createPost({
content: 'Check out this photo!',
mediaItems: [
{ type: 'image', url: 'https://cdn.example.com/photo.jpg' }
],
platforms: [
{ platform: 'twitter', accountId: 'YOUR_ACCOUNT_ID' }
],
publishNow: true
});
console.log('Tweet with image posted!', post._id);result = client.posts.create(
content="Check out this photo!",
media_items=[
{"type": "image", "url": "https://cdn.example.com/photo.jpg"}
],
platforms=[
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
publish_now=True
)
post = result.post
print(f"Tweet with image posted! {post['_id']}")curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Check out this photo!",
"mediaItems": [
{"type": "image", "url": "https://cdn.example.com/photo.jpg"}
],
"platforms": [
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
"publishNow": true
}'Tweet with Video
Attach a single video per tweet. MP4 and MOV formats, up to 512 MB, max 140 seconds.
const { post } = await late.posts.createPost({
content: 'New product demo',
mediaItems: [
{ type: 'video', url: 'https://cdn.example.com/demo.mp4' }
],
platforms: [
{ platform: 'twitter', accountId: 'YOUR_ACCOUNT_ID' }
],
publishNow: true
});
console.log('Tweet with video posted!', post._id);result = client.posts.create(
content="New product demo",
media_items=[
{"type": "video", "url": "https://cdn.example.com/demo.mp4"}
],
platforms=[
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
publish_now=True
)
post = result.post
print(f"Tweet with video posted! {post['_id']}")curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "New product demo",
"mediaItems": [
{"type": "video", "url": "https://cdn.example.com/demo.mp4"}
],
"platforms": [
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
"publishNow": true
}'Tweet with GIF
Only 1 GIF per tweet (it consumes all 4 image slots). Max 15 MB, 1280 x 1080 px. Animated GIFs auto-play in the timeline.
const { post } = await late.posts.createPost({
content: 'Check out this animation!',
mediaItems: [
{ type: 'gif', url: 'https://cdn.example.com/animation.gif' }
],
platforms: [
{ platform: 'twitter', accountId: 'YOUR_ACCOUNT_ID' }
],
publishNow: true
});
console.log('Tweet with GIF posted!', post._id);result = client.posts.create(
content="Check out this animation!",
media_items=[
{"type": "gif", "url": "https://cdn.example.com/animation.gif"}
],
platforms=[
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
publish_now=True
)
post = result.post
print(f"Tweet with GIF posted! {post['_id']}")curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Check out this animation!",
"mediaItems": [
{"type": "gif", "url": "https://cdn.example.com/animation.gif"}
],
"platforms": [
{"platform": "twitter", "accountId": "YOUR_ACCOUNT_ID"}
],
"publishNow": true
}'Thread (Multi-Tweet)
Create Twitter threads with multiple connected tweets using platformSpecificData.threadItems. Each item becomes a reply to the previous tweet and can have its own content and media.
Reply Tweets
Use platformSpecificData.replyToTweetId to publish a tweet as a reply to an existing tweet.
Note:
replyToTweetIdcannot be combined withreplySettings. For threads, only the first tweet replies to the target; subsequent tweets chain normally.
curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Replying via Late API",
"platforms": [{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"replyToTweetId": "1748391029384756102"
}
}],
"publishNow": true
}'const { post } = await late.posts.createPost({
content: 'Replying via Late API',
platforms: [{
platform: 'twitter',
accountId: 'YOUR_ACCOUNT_ID',
platformSpecificData: {
replyToTweetId: '1748391029384756102'
}
}],
publishNow: true
});
console.log('Reply posted!', post._id);result = client.posts.create(
content="Replying via Late API",
platforms=[{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"replyToTweetId": "1748391029384756102"
}
}],
publish_now=True
)
post = result.post
print(f"Reply posted! {post['_id']}")Reply Thread
To reply with a thread, combine replyToTweetId with threadItems. Only the first thread item replies to the target tweet.
curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platforms": [{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"replyToTweetId": "1748391029384756102",
"threadItems": [
{"content": "1/ Reply thread: first tweet replies to the target"},
{"content": "2/ Follow-up tweet in the same thread"}
]
}
}],
"publishNow": true
}'const { post } = await late.posts.createPost({
platforms: [{
platform: 'twitter',
accountId: 'YOUR_ACCOUNT_ID',
platformSpecificData: {
replyToTweetId: '1748391029384756102',
threadItems: [
{ content: '1/ Reply thread: first tweet replies to the target' },
{ content: '2/ Follow-up tweet in the same thread' }
]
}
}],
publishNow: true
});
console.log('Reply thread posted!', post._id);result = client.posts.create(
platforms=[{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"replyToTweetId": "1748391029384756102",
"threadItems": [
{"content": "1/ Reply thread: first tweet replies to the target"},
{"content": "2/ Follow-up tweet in the same thread"}
]
}
}],
publish_now=True
)
post = result.post
print(f"Reply thread posted! {post['_id']}")const { post } = await late.posts.createPost({
platforms: [{
platform: 'twitter',
accountId: 'YOUR_ACCOUNT_ID',
platformSpecificData: {
threadItems: [
{
content: '1/ Starting a thread about API design',
mediaItems: [{ type: 'image', url: 'https://cdn.example.com/image1.jpg' }]
},
{ content: '2/ First, always use proper HTTP methods...' },
{ content: '3/ Second, version your APIs from day one...' },
{ content: '4/ Finally, document everything! /end' }
]
}
}],
publishNow: true
});
console.log('Thread posted!', post._id);result = client.posts.create(
platforms=[{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"threadItems": [
{
"content": "1/ Starting a thread about API design",
"mediaItems": [{"type": "image", "url": "https://cdn.example.com/image1.jpg"}]
},
{"content": "2/ First, always use proper HTTP methods..."},
{"content": "3/ Second, version your APIs from day one..."},
{"content": "4/ Finally, document everything! /end"}
]
}
}],
publish_now=True
)
post = result.post
print(f"Thread posted! {post['_id']}")curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platforms": [{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"threadItems": [
{
"content": "1/ Starting a thread about API design",
"mediaItems": [{"type": "image", "url": "https://cdn.example.com/image1.jpg"}]
},
{
"content": "2/ First, always use proper HTTP methods..."
},
{
"content": "3/ Second, version your APIs from day one..."
},
{
"content": "4/ Finally, document everything! /end"
}
]
}
}],
"publishNow": true
}'Media Requirements
Images
| Property | Requirement |
|---|---|
| Max images | 4 per tweet |
| Formats | JPEG, PNG, WebP, GIF |
| Max file size | 5 MB (images), 15 MB (GIFs) |
| Min dimensions | 4 x 4 px |
| Max dimensions | 8192 x 8192 px |
| Recommended | 1200 x 675 px (16:9) |
Aspect Ratios
| Type | Ratio | Dimensions |
|---|---|---|
| Landscape | 16:9 | 1200 x 675 px |
| Square | 1:1 | 1200 x 1200 px |
| Portrait | 4:5 | 1080 x 1350 px |
GIFs
| Property | Requirement |
|---|---|
| Max per tweet | 1 (consumes all 4 image slots) |
| Max file size | 15 MB |
| Max dimensions | 1280 x 1080 px |
| Behavior | Auto-plays in timeline |
Videos
| Property | Requirement |
|---|---|
| Max videos | 1 per tweet |
| Formats | MP4, MOV |
| Max file size | 512 MB |
| Max duration | 140 seconds (2 min 20 sec) |
| Min duration | 0.5 seconds |
| Min dimensions | 32 x 32 px |
| Max dimensions | 1920 x 1200 px |
| Frame rate | 40 fps max |
| Bitrate | 25 Mbps max |
Recommended Video Specs
| Property | Recommended |
|---|---|
| Resolution | 1280 x 720 px (720p) |
| Aspect ratio | 16:9 (landscape) or 1:1 (square) |
| Frame rate | 30 fps |
| Codec | H.264 |
| Audio | AAC, 128 kbps |
Platform-Specific Fields
All fields go inside platformSpecificData on the Twitter platform entry.
| Field | Type | Description |
|---|---|---|
replyToTweetId | string | ID of an existing tweet to reply to. The published tweet will appear as a reply in that tweet's thread. For threads, only the first tweet replies to the target; subsequent tweets chain normally. |
replySettings | "following" | "mentionedUsers" | "subscribers" | "verified" | Controls who can reply to the tweet. Omit for default (everyone can reply). For threads, applies to the first tweet only. Cannot be combined with replyToTweetId. |
threadItems | Array<{content, mediaItems?}> | Creates a thread (multiple connected tweets). Each item becomes a reply to the previous tweet. Each item can have its own content and up to 4 images or 1 video. |
Media URL Requirements
These do not work as media URLs:
- Google Drive -- returns an HTML download page, not the file
- Dropbox -- returns an HTML preview page
- OneDrive / SharePoint -- returns HTML
- iCloud -- returns HTML
Test your URL in an incognito browser window. If you see a webpage instead of the raw image or video, it will not work.
Media URLs must be:
- Publicly accessible (no authentication required)
- Returning actual media bytes with the correct
Content-Typeheader - Not behind redirects that resolve to HTML pages
- Hosted on a fast, reliable CDN
Supabase URLs: Late auto-proxies Supabase storage URLs, so they work without additional configuration.
Analytics
Requires Analytics add-on
Available metrics via the Analytics API:
| Metric | Available |
|---|---|
| Impressions | ✅ |
| Likes | ✅ |
| Comments | ✅ |
| Shares | ✅ |
| Clicks | ✅ |
| Views | ✅ |
const analytics = await late.analytics.getAnalytics({
platform: 'twitter',
fromDate: '2024-01-01',
toDate: '2024-01-31'
});
console.log(analytics.posts);analytics = client.analytics.get(
platform="twitter",
from_date="2024-01-01",
to_date="2024-01-31"
)
print(analytics["posts"])curl "https://getlate.dev/api/v1/analytics?platform=twitter&fromDate=2024-01-01&toDate=2024-01-31" \
-H "Authorization: Bearer YOUR_API_KEY"Engagement
Retweet, bookmark, and follow directly through the API. All engagement endpoints share a 50 requests per 15-min window rate limit. Retweets also share the 300/3hr creation limit with tweet creation.
// Retweet
await late.twitterEngagement.retweetPost({
accountId: 'YOUR_ACCOUNT_ID',
tweetId: '1748391029384756102'
});
// Bookmark
await late.twitterEngagement.bookmarkPost({
accountId: 'YOUR_ACCOUNT_ID',
tweetId: '1748391029384756102'
});
// Follow
await late.twitterEngagement.followUser({
accountId: 'YOUR_ACCOUNT_ID',
targetUserId: '123456789'
});# Retweet
client.twitter_engagement.retweet_post(
account_id="YOUR_ACCOUNT_ID",
tweet_id="1748391029384756102"
)
# Bookmark
client.twitter_engagement.bookmark_post(
account_id="YOUR_ACCOUNT_ID",
tweet_id="1748391029384756102"
)
# Follow
client.twitter_engagement.follow_user(
account_id="YOUR_ACCOUNT_ID",
target_user_id="123456789"
)# Retweet
curl -X POST https://getlate.dev/api/v1/twitter/retweet \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"accountId": "YOUR_ACCOUNT_ID", "tweetId": "1748391029384756102"}'
# Bookmark
curl -X POST https://getlate.dev/api/v1/twitter/bookmark \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"accountId": "YOUR_ACCOUNT_ID", "tweetId": "1748391029384756102"}'
# Follow
curl -X POST https://getlate.dev/api/v1/twitter/follow \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"accountId": "YOUR_ACCOUNT_ID", "targetUserId": "123456789"}'| Action | Endpoint | Undo |
|---|---|---|
| Retweet | POST /v1/twitter/retweet | DELETE /v1/twitter/retweet |
| Bookmark | POST /v1/twitter/bookmark | DELETE /v1/twitter/bookmark |
| Follow | POST /v1/twitter/follow | DELETE /v1/twitter/follow |
See Twitter Engagement API Reference for full endpoint documentation.
What You Can't Do
These features are not available through Twitter's API:
- Edit tweets after posting
- Create polls
- Create Spaces
- Post to Communities
- Pin tweets to profile
- Add Twitter Cards (must be configured on the destination URL via meta tags)
- Upload videos longer than 140 seconds
- Post as a personal DM broadcast
Common Errors
Twitter/X has a 21.3% failure rate across Late's platform (17,385 failures out of 81,796 attempts). Here are the most frequent errors and how to fix them:
| Error | What it means | How to fix |
|---|---|---|
| "Tweet text is too long (X characters). Twitter's limit is 280 characters. Note: URLs count as 23 characters." | Exceeds 280 character limit for free accounts | Shorten text or use customContent for Twitter. Remember: URLs = 23 chars, emojis = 2 chars. |
| "X (Twitter) does not allow duplicate tweets" | Same or very similar content was already posted | Modify the text, even slightly. |
| "Rate limit hit. Please wait 10 minutes" | Late's velocity limit was triggered | Reduce posting frequency. Space posts at least 4 minutes apart. |
| "Missing tweet.write scope" / "forbidden" | OAuth token lacks required permissions | Reconnect the account with all required scopes. |
| Token expired | OAuth access was revoked or expired | Reconnect the account. Subscribe to the account.disconnected webhook to catch this proactively. |
Inbox
Requires Inbox add-on — Build: +$10/mo · Accelerate: +$50/unit · Unlimited: +$1,000/mo
Twitter/X supports DMs and comments through the unified Inbox API.
Direct Messages
| Feature | Supported |
|---|---|
| List conversations | ✅ |
| Fetch messages | ✅ |
| Send text messages | ✅ |
| Send attachments | ✅ (images, videos - max 25 MB) |
| Archive/unarchive | ❌ |
Comments
| Feature | Supported |
|---|---|
| List comments on posts | ✅ |
| Post new comment | ✅ |
| Reply to comments | ✅ |
| Delete comments | ✅ |
| Like/unlike comments | ✅ |
| Hide/unhide comments | ✅ |
Limitations
- DM permissions - DMs require
dm.readanddm.writescopes - Reply search - Uses cached conversation threads (2-min TTL) to manage rate limits
- Cached DMs - Conversations are cached with a 15-min TTL
See Messages and Comments API Reference for endpoint details.
Related Endpoints
- Connect Twitter Account - OAuth flow
- Create Post - Post creation and scheduling
- Upload Media - Image and video uploads
- Analytics - Post performance metrics
- Twitter Media Download - Download Twitter media
- Twitter Engagement - Retweet, bookmark, follow
- Messages and Comments