Twitter/X API
Schedule and automate Twitter/X posts with RelayAPI — tweets, threads, polls, images, videos, GIFs, and reply settings.
Quick Reference
| Property | Value |
|---|---|
| Platform key | twitter |
| Auth method | OAuth 2.0 |
| Character limit | 280 |
| 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 |
| Post types | Tweet, Thread, Reply, Poll |
| Scheduling | Yes |
| Analytics | Yes |
Client options — TypeScript · Python REST/OpenAPI · Go · Java · REST API
Before You Start
RelayAPI's X publisher enforces a strict 280 character limit. It does not expose Premium long-form posting. URLs always count as 23 characters regardless of actual length, and emojis count as 2 characters. If you are cross-posting from platforms with higher limits (LinkedIn at 3,000, Facebook at 63,000), use target_options.twitter.content to provide a shorter version or your post will fail. Duplicate tweets are rejected by X, even when the text is very similar.
Published Post editing uses tweet.write; like/unlike requires like.write,
and reply hide/unhide requires tweet.moderate.write. Reauthorize an older X
connection that lacks any required grant; refresh cannot add scopes that were
never granted.
Quick Start
Post a tweet immediately:
import Relay from '@relayapi/sdk';
const client = new Relay();
const post = await client.posts.create({
content: 'Hello from RelayAPI!',
targets: ['twitter'],
scheduled_at: 'now',
});
console.log(post.id); // post_abc123Content Types
Text Tweet
Simple text-only tweet. RelayAPI enforces a 280-character limit.
const post = await client.posts.create({
content: 'Just shipped a new feature! Check it out.',
targets: ['twitter'],
scheduled_at: 'now',
});Tweet with Images
Up to 4 images per tweet. Cannot mix images and videos. Cannot mix images and GIFs.
const post = await client.posts.create({
content: 'Check out these photos!',
targets: ['twitter'],
media: [
{ url: 'https://cdn.example.com/photo1.jpg', type: 'image' },
{ url: 'https://cdn.example.com/photo2.jpg', type: 'image' },
],
scheduled_at: 'now',
});Tweet with Video
Single video per tweet. MP4 or MOV, up to 512 MB, max 140 seconds.
const post = await client.posts.create({
content: 'Watch this!',
targets: ['twitter'],
media: [
{ url: 'https://cdn.example.com/video.mp4', type: 'video' },
],
scheduled_at: 'now',
});Tweet with GIF
1 GIF per tweet (consumes all 4 image slots). Max 15 MB, 1280 x 1080 px. Auto-plays in timeline.
const post = await client.posts.create({
content: 'Check this out!',
targets: ['twitter'],
media: [
{ url: 'https://cdn.example.com/animation.gif', type: 'gif' },
],
scheduled_at: 'now',
});Thread (Multi-Tweet)
Create Twitter threads with multiple connected tweets using target_options.twitter.thread. Each item becomes a reply to the previous tweet and can have its own content and media.
const post = await client.posts.create({
targets: ['twitter'],
scheduled_at: 'now',
target_options: {
twitter: {
thread: [
{ content: '1/ Starting a thread about API design...' },
{ content: '2/ First, always use proper HTTP methods.' },
{
content: '3/ Second, version your APIs from day one.',
media: [{ url: 'https://cdn.example.com/diagram.png', type: 'image' }],
},
{ content: '4/ Finally, document everything! /end' },
],
},
},
});Each thread item has its own 280-character limit. Thread replies are posted sequentially since each one needs the parent tweet ID.
Reply to Tweet
Reply to an existing tweet using reply_to. Provide the tweet ID of the tweet you want to reply to.
const post = await client.posts.create({
content: "Great point! Here's my take...",
targets: ['twitter'],
scheduled_at: 'now',
target_options: {
twitter: {
reply_to: '1748391029384756102',
},
},
});Reply Settings
Control who can reply to your tweet using reply_settings.
const post = await client.posts.create({
content: 'Important announcement for our followers.',
targets: ['twitter'],
scheduled_at: 'now',
target_options: {
twitter: {
reply_settings: 'following',
},
},
});reply_to cannot be combined with reply_settings. For threads, reply settings apply to the first tweet only.
Poll
Create a tweet with a poll using target_options.twitter.poll. Polls support 2-4 options (each up to 25 characters) and a duration from 5 minutes to 7 days.
const post = await client.posts.create({
content: 'What should we build next?',
targets: ['twitter'],
scheduled_at: 'now',
target_options: {
twitter: {
poll: {
options: ['Dark mode', 'New analytics', 'More integrations'],
duration_minutes: 1440,
},
},
},
});Polls cannot be combined with media attachments or threads. Each poll option must be 1-25 characters. Duration must be between 5 and 10,080 minutes (7 days).
Media Requirements
Images
| Property | Requirement |
|---|---|
| Max per tweet | 4 |
| Formats | JPEG, PNG, WebP |
| Max file size | 5 MB |
| 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 per tweet | 1 |
| 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 |
target_options Fields
All fields go inside target_options.twitter on your post request.
| Field | Type | Description |
|---|---|---|
content | string | Override post content for Twitter specifically |
media | object[] | Override media for Twitter specifically |
thread | object[] | Array of {content, media?} for multi-tweet threads. Each item becomes a reply to the previous tweet. |
reply_to | string | Tweet ID to reply to. The published tweet appears as a reply in that tweet's thread. |
reply_settings | string | Who can reply: "following", "mentionedUsers", "subscribers", "verified". Cannot be combined with reply_to. |
poll | object | Poll with options (array of 2-4 strings, each 1-25 chars) and duration_minutes (5-10,080). Cannot be combined with media or threads. |
place_id | string | X place ID forwarded as geo.place_id; single-post publishing only |
sensitive_media_warning | object | Exact adult_content, graphic_violence, and other booleans applied to every uploaded attachment; requires media and is single-post only |
community_id | string | X Community destination |
tagged_user_ids | string[] | Up to 10 users tagged in uploaded media |
paid_partnership | boolean | Paid-partnership disclosure |
made_with_ai | boolean | AI-content disclosure |
share_with_followers | boolean | Share a Community post with followers where X permits it |
await client.posts.create({
content: 'Field update from the launch site',
targets: ['twitter'],
scheduled_at: 'now',
media: [{ url: imageURL, type: 'image' }],
target_options: {
twitter: {
place_id: '01a9a39529b27f36',
sensitive_media_warning: {
adult_content: false,
graphic_violence: false,
other: true,
},
},
},
});Character Counting
Twitter uses weighted character counting:
- URLs always count as 23 characters regardless of actual length (t.co shortening)
- Emojis count as 2 characters each
- All other characters count as 1
A tweet with a 200-character URL still only uses 23 of your 280-character budget. But a tweet with 260 characters of text plus one URL would be 283 characters (260 + 23), exceeding the limit.
Common Errors
| Error | Cause | Fix |
|---|---|---|
| Character limit exceeded | Content exceeds 280 characters | Use target_options.twitter.content to provide a shorter version. Remember: URLs = 23 chars, emojis = 2 chars. |
| Duplicate content | Same or very similar text posted recently | Modify the text, even slightly. Twitter rejects near-duplicate tweets. |
| Rate limit hit | Too many posts in a short window | Space posts at least 4 minutes apart. Limit is ~300 tweets per 3-hour window. |
| Media processing failed | Unsupported format or corrupt file | Verify media format and file integrity. |
| Missing tweet.write scope | OAuth token lacks required permissions | Reconnect the account with all required scopes. |
| Token expired | OAuth access was revoked or expired | Reconnect the account via the dashboard or Connect API. |
| INVALID_POLL | Poll validation failed | Ensure 2-4 options (each 1-25 chars), duration 5-10,080 minutes, and no media or thread attached. |
Known Quirks
- Duplicate tweets are rejected — even very similar content gets blocked. Modify text meaningfully between posts.
- URLs always count as 23 characters regardless of actual length due to t.co shortening.
- Cannot mix images and videos in the same tweet.
- Cannot mix images and GIFs in the same tweet.
- GIF consumes all 4 image slots — you cannot attach a GIF alongside other images.
- No Premium long-form path — RelayAPI validates every X post and thread item against 280 characters.
- Rate limit — approximately 300 tweets per 3-hour window for creation.
- Thread replies must be posted sequentially since each reply needs the parent tweet ID.
- Emojis count as 2 characters — a tweet with 140 emojis uses all 280 characters.
- Polls cannot have media or threads — polls are mutually exclusive with media attachments and thread items.
Published edits and social actions
RelayAPI can edit a published X Post's text, edit an owned reply, hide/unhide a reply, and like/unlike a Post. An X edit creates a replacement Post ID; RelayAPI updates the target and retains the old ID in edit history. Every action requires an idempotency key and produces a durable operation. See Published Edits and Social Actions for the typed SDK calls, exact action matrix, and unknown-outcome handling.
Automations
X (Twitter) is a Tier 1 automation platform with tier-gated webhooks (Pay-per-Use: 3 DM conversation subs + 1 webhook).
Triggers
| Type | Fires on |
|---|---|
twitter_dm | Inbound DM |
twitter_mention | @-mention |
twitter_reply | Reply to your tweet |
twitter_follow | New follower |
twitter_like | Someone likes your tweet |
twitter_retweet | Someone retweets |
twitter_quote | Someone quotes your tweet |
Send nodes
Base: https://api.x.com/2.
| Node | Endpoint | Required fields |
|---|---|---|
twitter_send_dm | POST /dm_conversations/with/{user}/messages | text |
twitter_send_dm_media | POST /dm_conversations/with/{user}/messages | media_id (upload first via media endpoint), optional text |
twitter_reply_to_tweet | POST /tweets with reply.in_reply_to_tweet_id | text, tweet_id |
twitter_like_tweet | POST /users/{id}/likes | tweet_id |
twitter_retweet | POST /users/{id}/retweets | tweet_id |
Found something wrong? Help us improve this page.