Platforms

X

Post to X with threads, replies, quotes, polls and media, edit recent posts, retweet, bookmark and follow, send DMs and read post analytics.

Publish posts, threads, replies, quotes, polls and long-form Articles to X, edit recent posts, and act as the account (retweet, bookmark, follow, search). The same connection reads post analytics, sends and receives DMs and manages replies to your posts. In the API the platform id is twitter; everywhere a person reads it, show X.

#What you can do

Capability Supported Where
Publish Text, up to 4 images, 1 GIF or 1 video, threads, replies, quotes, polls, Articles Create a post
Schedule Yes Scheduling
Edit after publishing Text, within 1 hour, Premium accounts Edit
Engagement Retweet, bookmark, follow, search recent posts, look up a post X Engagement endpoints
Analytics Impressions, likes, replies, reposts, bookmarks, clicks, media views Platform insights
DMs Start conversations, reply, attachments Messages
Comments Replies to your posts: list, reply, like, hide, delete Comments
Ads Through an X Ads connection X Ads

#Connect

A card on file is required

X charges for every API call, starting with the first one, so Pozy bills X usage per call at the rates on the pricing page. Until the workspace has a payment method, GET /v1/connect/twitter returns 402 with type: "payment_required", reason: "twitter_passthrough" and a dashboard_url where the user can add a card. This applies on the free tier too. Rates are on Pricing.

With a card on file, call the connect endpoint with a profileId and send the user to authUrl. After they approve, they land on your redirect_url with connected=twitter, profileId and accountId appended.

curl "https://api.pozy.ai/v1/connect/twitter?profileId=6720c0f24f3b2e0018d4a6e0&redirect_url=https://app.example.com/connected" \
  -H "Authorization: Bearer $POZY_API_KEY"

If the user denies consent, they come back with error=oauth_denied&platform=twitter on the redirect URL. Treat any other error value as a generic failure.

#Spend cap

Each workspace can set a monthly cap on X spend in billing settings. Pozy emails a warning at 80% of the cap. At the cap, every billable X call (posts, DMs, reads) is refused before it reaches X with 402 and code x_spend_cap_reached, until the cap is raised or the month rolls over. Drafts never count.

#Post types

curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Release 2.4 is out: faster sync and a new export screen.",
    "mediaItems": [{ "type": "image", "url": "https://cdn.example.com/release-2-4.png" }],
    "platforms": [{ "platform": "twitter", "accountId": "6720c1a94f3b2e0018d4a7f6" }],
    "publishNow": true
  }'
Cross-posting long text

A free X account allows 280 characters. When the same post goes to LinkedIn or Facebook too, give the X entry its own customContent under the limit, or the X copy fails with "Tweet text is too long" while the others publish.

#Thread

Put the whole sequence in platformSpecificData.threadItems. Item 0 is the first post and each item replies to the one before. Each item can have its own mediaItems. When threadItems is set, the top-level content is kept for search and display in Pozy but is not published, so the first post must be threadItems[0].

Thread
{
  "platform": "twitter",
  "accountId": "6720c1a94f3b2e0018d4a7f6",
  "platformSpecificData": {
    "threadItems": [
      { "content": "How we halved our cloud bill in 6 weeks. A thread.", "mediaItems": [{ "type": "image", "url": "https://cdn.example.com/bill-chart.png" }] },
      { "content": "1. We turned off every environment nobody had logged into for 30 days." },
      { "content": "2. We moved batch jobs to spot instances." },
      { "content": "3. We set budgets per team, with alerts. That one changed behaviour fastest." }
    ]
  }
}

A thread cannot carry a poll.

#Reply and quote

  • Reply: replyToTweetId with the id of the post. X only accepts replies to your own posts or posts that mention you. Combine it with threadItems to reply with a whole thread; only the first item replies to the target. It cannot be combined with replySettings.
  • Quote: quoteTweetId with the post id or its full URL. X only accepts quotes of your own posts, posts that mention you, or posts in a conversation you are part of. A quote cannot carry media or a poll. A quote made this way is billed at the normal post rate, while pasting the post's URL into content is billed as a post with a link, which costs much more.

To find ids, use Search recent tweets (the last 7 days) or resolve a URL with Look up a tweet.

#Poll

poll takes 2 to 4 options of 1 to 25 characters and duration_minutes from 5 to 10,080 (7 days). It cannot be combined with media, threadItems or quoteTweetId.

Poll
"platformSpecificData": {
  "poll": { "options": ["Dark mode", "Offline sync", "More exports"], "duration_minutes": 1440 }
}

#Article

article publishes a long-form X Article on an eligible Premium+ account. It needs a title and a content_state in X's own block format (blocks with types such as unstyled, header-one, ordered-list-item, plus entities for links and images). mode: "draft" stops after creating the draft on X; publish (default) publishes it and makes two billable X calls. An optional cover.url takes a public JPG, PNG or WebP. Articles cannot be combined with any other post option.

#Platform fields

Schema: TwitterPlatformData.

Field Notes
threadItems The full thread, first post first.
replyToTweetId Post to reply to.
quoteTweetId Post id or URL to quote.
replySettings Who can reply: following, mentionedUsers, subscribers or verified. Everyone when left out. First post only in a thread.
poll { options, duration_minutes }.
mediaTaggedUsers Up to 10 handles to tag in the attached media.
geoRestriction.countries Hides the media (not the text) outside up to 25 countries. Ignored without media.
sensitiveMedia { adultContent, graphicViolence, other }, at least one true. Adds a warning to every attached item.
paidPartnership Paid promotion label. First post only. Depends on X's access tier.
madeWithAi AI-generated media label (images and video, not text). First post only.
longVideo Uses X's long-video upload path. Applied only on accounts with a paid X subscription and ignored otherwise; some accounts also need allowlisting by X.
article Long-form Article (see above).

#Edit a published post

Edit published post with platform: "twitter" changes the text, but X only allows it on accounts with X Premium, within 1 hour of publishing, up to 5 times, on single posts (not threads), and for text only. X creates a new post id on every edit and Pozy returns it as id. Outside those rules the call returns 400.

Unpublish deletes a published post from X.

#Media requirements

A post carries up to 4 images, or 1 GIF, or 1 video. Use direct file URLs or upload the file.

Images GIF Video
Formats JPEG, PNG, WebP, GIF GIF MP4, MOV
Max size 5 MB 15 MB 512 MB
Count Up to 4 1 (takes all slots) 1
Dimensions 4 x 4 to 8192 x 8192 Up to 1280 x 1080 32 x 32 to 1920 x 1200
Duration From 0.5 seconds; the maximum is set by X per account
Other 1200 x 675 (16:9) recommended Auto-plays Up to 40 fps and 25 Mbps; 1280 x 720, 30 fps, H.264, AAC recommended

Send a GIF as type: "gif".

#Limits

Limit Value
Post text 280 characters (free), 25,000 (Premium). URLs count as 23, emojis as 2.
Posts per account per hour 25. Extra posts are retried when the hour resets.
DMs 200 requests per 15 minutes and 1,000 per 24 hours per account
Poll options 2 to 4, 25 characters each
Edits 5 within 1 hour, Premium only

If X rate-limits an account, Pozy pauses publishing on it for 10 minutes, doubling on each further hit within 24 hours. Posts scheduled inside the pause fail with the same message. X rejects text it already has from the account, so repeated posts need changes.

The API cannot use Spaces, post to Communities, pin a post to the profile, create cards (use meta tags on the linked page) or send broadcast DMs.

#Analytics

GET /v1/analytics?platform=twitter returns impressions, likes, comments, shares, saves (bookmarks), clicks and views per post. views is X's media view count, so text and image posts report 0. The date range defaults to the last 90 days and can be up to 366 days. Every analytics read is an X call billed at X's rate.

#Engagement

Act as a connected account with the X Engagement endpoints. Each POST has a DELETE on the same path to undo it.

Action Call Body
Retweet POST /v1/twitter/retweet accountId, tweetId
Bookmark POST /v1/twitter/bookmark accountId, tweetId
Follow POST /v1/twitter/follow accountId, targetUserId. Protected accounts return pending_follow: true.

A 403 means X refused the action for this account, usually a missing permission or a suspended account. Reconnect and accept every permission.

#Inbox

DMs. Start a conversation with Create conversation, passing accountId, the recipient as participantUsername or participantId, and message. An existing conversation with that person is reused. Pozy first checks that the person accepts DMs from you and returns 422 DM_NOT_ALLOWED if not; pass skipDmCheck: true when you have already checked.

Send a DM
curl -X POST https://api.pozy.ai/v1/inbox/conversations \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountId": "6720c1a94f3b2e0018d4a7f6", "participantUsername": "acme_support", "message": "Thanks for flagging this. Which screen showed the error?" }'

Later messages go through Send message and can include images or video up to 25 MB. Conversation lists are cached for 2 minutes. X's newer encrypted chat is not visible to any third-party app, so some conversations may look empty or show only your side. X DMs are billed at X's rate, not on Pozy's outbound message meter.

Comments are the replies to your posts. Reply with Reply to comment, adding commentId to answer a specific reply. postId can be the Pozy post id or the X post id, so posts made outside Pozy work too. You can also like, hide and delete replies. Send an Idempotency-Key when you retry, so a retry never posts twice.

#Errors you may see

Message Cause Fix
402 payment_required, reason: "twitter_passthrough" No card on file Add a payment method at dashboard_url, then connect again.
402 x_spend_cap_reached The workspace's monthly X cap is used up Raise or remove the cap in billing.
Tweet text is too long Over 280 characters on a free account Shorten the text or set customContent.
X does not allow duplicate tweets Same text posted before Change the text.
Rate limit hit. Please wait 10 minutes X rate-limited the account Wait for the pause to end.
Hourly limit reached (25/25) Pozy's per-account hourly limit Nothing; the post is retried when the hour resets.
422 DM_NOT_ALLOWED The recipient does not accept your DMs They need to open DMs to you first.
401 on later calls X revoked the session Reconnect the account.

A rejected publishNow: true post returns 207 with post.status: "failed" and the reason in platforms[].errorMessage. A failed X post is not retried automatically.

#Endpoints

Endpoint What it does
POST /v1/twitter/retweet Retweet a post
DELETE /v1/twitter/retweet Undo retweet
POST /v1/twitter/bookmark Bookmark a tweet
DELETE /v1/twitter/bookmark Remove bookmark
POST /v1/twitter/follow Follow a user
DELETE /v1/twitter/follow Unfollow a user
GET /v1/twitter/search Search recent tweets
GET /v1/twitter/tweet Look up a tweet
POST /v1/inbox/conversations Create conversation
POST /v1/inbox/conversations/{conversationId}/messages Send message
POST /v1/inbox/comments/{postId} Reply to comment
POST /v1/posts/{postId}/edit Edit published post
POST /v1/posts/{postId}/unpublish Unpublish post

#Webhook events

Event When it fires
post.published Post published.
post.failed Post failed.
post.platform.published Post platform published.
post.platform.failed Post platform failed.
post.platform.deleted Post platform deleted.
account.disconnected Account disconnected.
comment.received Comment received.