X (Twitter)
Connect an X account and publish text, images, or video with the PostZen API.
PostZen publishes text, image, and video posts to X (Twitter). You can publish immediately, schedule a post, or save it as a draft through the same posts API used for every other platform.
Connecting an X account requires a payment method on file, even if you have not reached the free tier's two-account limit. A connection attempt without one returns a 402.
Quick reference
| Requirement | Value |
|---|---|
| Platform value | x or twitter; responses normalize it to twitter |
| Text | 280 characters maximum for every post |
| Post types | Text, 1–4 images, or 1 video |
| Image size | 5 MB maximum per image |
| Video size | 512 MB maximum |
| Post media | Images and video cannot be mixed in one post |
| Presigned image upload types | JPEG/JPG, PNG, WebP, GIF |
| Presigned video upload types | MP4, MPEG, QuickTime, AVI, WebM, M4V |
| Scheduling | Supported; scheduledFor must be an ISO-8601 timestamp at least 60 seconds in the future |
Before you start
- Add a payment method before starting the X connection flow. X connections always require one.
- PostZen connects X accounts through OAuth 2.0. The returned OAuth
stateexpires after 10 minutes, so send the user to the returnedauthUrlpromptly. - Keep every X post at or below 280 characters. Premium and long-form posts are not supported.
- Use either images or video in a post. X posts cannot contain both.
Media URLs must be publicly accessible direct links to the file. Google Drive, Dropbox, OneDrive, and iCloud share links return HTML pages instead of the media file. Upload the file through /v1/media/presign when you do not have a direct URL. External media URLs can be up to 100 MB; PostZen downloads and re-hosts them, while the X-specific size limits still apply.
Connect your account
Request a connect URL for the profile that will own the X account, then send the user to the returned authUrl. You can use twitter or x as the platform path value.
const { data } = await postzen.connect.createConnectUrl({
path: { platform: 'twitter' },
query: {
profileId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
redirectUrl: 'https://yourapp.com/connected',
},
});
console.log(data.authUrl); // send the user here
console.log(data.state); // expires after 10 minutesresponse = client.connect.create_connect_url(
"twitter",
profile_id="jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
redirect_url="https://yourapp.com/connected",
)
print(response.authUrl) # send the user here
print(response.state) # expires after 10 minutescurl "https://api.postzen.dev/v1/connect/twitter?profileId=jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e&redirectUrl=https://yourapp.com/connected" \
-H "Authorization: Bearer $POSTZEN_API_KEY"The response is { authUrl, state }. The user completes the OAuth flow at authUrl.
Quick start
Publish a text post immediately by targeting the connected account's PostZen account ID.
const { data } = await postzen.posts.createPost({
body: {
content: 'Shipping today. Follow along for updates.',
publishNow: true,
platforms: [
{
platform: 'twitter',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
},
],
},
});
console.log(data.post._id);response = client.posts.create_post(
content="Shipping today. Follow along for updates.",
publish_now=True,
platforms=[
{
"platform": "twitter",
"account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
},
],
)
print(response.post.field_id)curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Shipping today. Follow along for updates.",
"publishNow": true,
"platforms": [
{
"platform": "twitter",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e"
}
]
}'Set exactly one creation mode: publishNow, scheduledFor, or isDraft.
Content types
X supports text posts, image posts, and video posts through PostZen.
Text posts
Send text in content. The 280-character limit applies to every X post, including premium accounts and cross-posted content.
{
"content": "A short update for X.",
"publishNow": true,
"platforms": [
{
"platform": "twitter",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e"
}
]
}Image posts
Add one to four image URLs to mediaItems. Each image must be no larger than 5 MB.
const { data } = await postzen.posts.createPost({
body: {
content: 'A look at the new release.',
publishNow: true,
mediaItems: [
{ url: 'https://cdn.example.com/release-1.jpg' },
{ url: 'https://cdn.example.com/release-2.jpg' },
],
platforms: [
{
platform: 'twitter',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
},
],
},
});
console.log(data.post._id);response = client.posts.create_post(
content="A look at the new release.",
publish_now=True,
media_items=[
{"url": "https://cdn.example.com/release-1.jpg"},
{"url": "https://cdn.example.com/release-2.jpg"},
],
platforms=[
{
"platform": "twitter",
"account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
},
],
)
print(response.post.field_id)curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "A look at the new release.",
"publishNow": true,
"mediaItems": [
{ "url": "https://cdn.example.com/release-1.jpg" },
{ "url": "https://cdn.example.com/release-2.jpg" }
],
"platforms": [
{
"platform": "twitter",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e"
}
]
}'Video posts
Add one video URL to mediaItems. An X video must be no larger than 512 MB.
const { data } = await postzen.posts.createPost({
body: {
content: 'Watch the release walkthrough.',
publishNow: true,
mediaItems: [
{ url: 'https://cdn.example.com/release.mp4' },
],
platforms: [
{
platform: 'twitter',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
},
],
},
});
console.log(data.post._id);response = client.posts.create_post(
content="Watch the release walkthrough.",
publish_now=True,
media_items=[
{"url": "https://cdn.example.com/release.mp4"},
],
platforms=[
{
"platform": "twitter",
"account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
},
],
)
print(response.post.field_id)curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Watch the release walkthrough.",
"publishNow": true,
"mediaItems": [
{ "url": "https://cdn.example.com/release.mp4" }
],
"platforms": [
{
"platform": "twitter",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e"
}
]
}'Media requirements
Images
| Requirement | Value |
|---|---|
| Images per post | 1–4 |
| Maximum size | 5 MB per image |
| Presigned upload types | JPEG/JPG, PNG, WebP, GIF |
| Oversize behavior | PostZen returns a warning; X may reject the image |
Videos
| Requirement | Value |
|---|---|
| Videos per post | 1 |
| Maximum size | 512 MB |
| Presigned upload types | MP4, MPEG, QuickTime, AVI, WebM, M4V |
Do not combine images and video in the same X post.
Platform settings
Pass X-specific settings in the target object's settings field.
| Setting | Type | Notes |
|---|---|---|
replySettings | "following" | "mentionedUsers" | Restricts replies to accounts you follow or users mentioned in the post. Omit it to allow everyone to reply. |
const { data } = await postzen.posts.createPost({
body: {
content: 'Product update: the new version is available.',
publishNow: true,
platforms: [
{
platform: 'twitter',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
settings: {
replySettings: 'following',
},
},
],
},
});PostZen analytics is live for X, including post metrics, follower counts, and best-time-to-post suggestions. X meters every API read, so analytics reads appear as X API pass-through usage. Link-click data is available only for your own posts from the last 30 days, and X does not report reach.
What you can't do
- Publish more than 280 characters, including premium or long-form posts.
- Create multi-post threads or tweetstorms.
- Mix images and video in one post.
- Create polls or quote posts.
- Read or manage DMs, inbox messages, comments, or replies through PostZen.
- Edit a post after it has been published.
- Manage ads or receive engagement webhooks.
Common errors
| Error | Meaning | Fix |
|---|---|---|
| Text exceeds 280 characters | X applies the 280-character hard limit to every post. This often affects content cross-posted from a platform with a higher limit. | Add a shorter customContent value to the X target. |
| Images and video are included together | X does not accept mixed image and video media in one post. | Create an image-only post or a video-only post. |
402 while connecting | The account connection was attempted without a payment method on file. | Add a payment method, then request a new connect URL. |
| OAuth state expired | More than 10 minutes passed before the OAuth flow completed. | Request a new connect URL and complete the flow within 10 minutes. |