PostZen

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

RequirementValue
Platform valuex or twitter; responses normalize it to twitter
Text280 characters maximum for every post
Post typesText, 1–4 images, or 1 video
Image size5 MB maximum per image
Video size512 MB maximum
Post mediaImages and video cannot be mixed in one post
Presigned image upload typesJPEG/JPG, PNG, WebP, GIF
Presigned video upload typesMP4, MPEG, QuickTime, AVI, WebM, M4V
SchedulingSupported; 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 state expires after 10 minutes, so send the user to the returned authUrl promptly.
  • 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 minutes
response = 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 minutes
curl "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

RequirementValue
Images per post1–4
Maximum size5 MB per image
Presigned upload typesJPEG/JPG, PNG, WebP, GIF
Oversize behaviorPostZen returns a warning; X may reject the image

Videos

RequirementValue
Videos per post1
Maximum size512 MB
Presigned upload typesMP4, 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.

SettingTypeNotes
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

ErrorMeaningFix
Text exceeds 280 charactersX 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 togetherX does not accept mixed image and video media in one post.Create an image-only post or a video-only post.
402 while connectingThe account connection was attempted without a payment method on file.Add a payment method, then request a new connect URL.
OAuth state expiredMore than 10 minutes passed before the OAuth flow completed.Request a new connect URL and complete the flow within 10 minutes.

On this page