Twitter / X
Post tweets, threads, polls, and media to Twitter/X via the PostPeer API.
Overview
Publish tweets, threads, polls, and rich media to Twitter/X through a single API endpoint. PostPeer handles OAuth, token refreshes, and rate limits.
X is the most expensive API PostPeer talks to, and it's the only platform with non-uniform credit pricing. X bills us per request, and posts containing a link are priced at roughly 13× the rate of plain text. We pass that through:
- Post on X (no URL): 5 credits
- Post on X with a URL: 50 credits
- Every other platform: 1 credit per post
If you're cross-posting to several platforms, expect the X line to dominate your usage. See Pricing below for the full breakdown.
Quick Start
1. Connect a Twitter Account
curl https://api.postpeer.dev/v1/connect/twitter \
-H "x-access-key: YOUR_API_KEY"Response:
{
"url": "https://twitter.com/i/oauth2/authorize?..."
}Redirect the user to the url. After authorization, the account is connected to your project. See Connect Accounts for the full OAuth flow.
2. Get the Account ID
curl https://api.postpeer.dev/v1/connect/integrations \
-H "x-access-key: YOUR_API_KEY"Find the integration with "platform": "twitter" and note the id.
3. Post a Tweet
curl -X POST "https://api.postpeer.dev/v1/posts" \
-H "x-access-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello from PostPeer!",
"platforms": [
{ "platform": "twitter", "accountId": "abc123" }
],
"publishNow": true
}'import PostPeer from '@postpeer/node';
const client = new PostPeer();
const { data } = await client.posts.create({
body: {
content: 'Hello from PostPeer!',
platforms: [{ platform: 'twitter', accountId: 'abc123' }],
publishNow: true,
},
});from postpeer import PostPeer
with PostPeer() as client:
post = client.posts.create(
content="Hello from PostPeer!",
platforms=[{"platform": "twitter", "accountId": "abc123"}],
publish_now=True,
)Response:
{
"success": true,
"status": "published",
"postId": "post_xyz789",
"platforms": [
{
"platform": "twitter",
"success": true,
"platformPostUrl": "https://twitter.com/you/status/123456"
}
]
}Features
Text Posts
Post a standard tweet with up to 280 characters.
Images & Videos
Attach media using the mediaItems array:
curl -X POST "https://api.postpeer.dev/v1/posts" \
-H "x-access-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Check out this photo!",
"platforms": [
{ "platform": "twitter", "accountId": "abc123" }
],
"mediaItems": [
{ "type": "image", "url": "https://example.com/photo.jpg" }
],
"publishNow": true
}'Supported media:
- Images: JPG, PNG, GIF, WebP (up to 4 per tweet)
- Videos: MP4 (1 per tweet)
- GIFs: animated GIF (1 per tweet)
Threads
Create multi-tweet threads using platformSpecificData.threadItems:
curl -X POST "https://api.postpeer.dev/v1/posts" \
-H "x-access-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "1/ Here'\''s a thread about our API...",
"platforms": [
{
"platform": "twitter",
"accountId": "abc123",
"platformSpecificData": {
"threadItems": [
{ "text": "2/ Connect any social account via OAuth" },
{ "text": "3/ Post to all platforms with one request" }
]
}
}
],
"publishNow": true
}'Polls
Create polls with 2-4 options:
curl -X POST "https://api.postpeer.dev/v1/posts" \
-H "x-access-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "What should we build next?",
"platforms": [
{
"platform": "twitter",
"accountId": "abc123",
"platformSpecificData": {
"poll": {
"options": ["Analytics", "Webhooks", "Bulk upload"],
"durationMinutes": 1440
}
}
}
],
"publishNow": true
}'Community Posts
Publish a tweet into a Twitter Community by providing platformSpecificData.communityId. Optionally set shareWithFollowers to true to also share the post with your followers.
curl -X POST "https://api.postpeer.dev/v1/posts" \
-H "x-access-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Excited to share this with the community!",
"platforms": [
{
"platform": "twitter",
"accountId": "abc123",
"platformSpecificData": {
"communityId": "1234567890",
"shareWithFollowers": true
}
}
],
"publishNow": true
}'| Field | Type | Description |
|---|---|---|
communityId | string | The ID of the Twitter Community to post into |
shareWithFollowers | boolean | Also share the Community post with your followers (default false) |
Reply to a Tweet
Post a tweet as a reply to an existing tweet by providing platformSpecificData.replyToTweetId:
curl -X POST "https://api.postpeer.dev/v1/posts" \
-H "x-access-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Great point!",
"platforms": [
{
"platform": "twitter",
"accountId": "abc123",
"platformSpecificData": {
"replyToTweetId": "1234567890"
}
}
],
"publishNow": true
}'Replying is not always available. It depends on the reply settings the original author picked for their tweet (e.g. everyone, people you follow, verified accounts, or mentioned users only). If your account doesn't match the author's selection, the reply will fail.
Reply Settings
Control who can reply to your tweet using platformSpecificData.replySettings:
- Omit
replySettingsto allow everyone (default) "following""mentionedUsers""subscribers""verified"
Long Posts
By default PostPeer rejects tweets over 280 characters. If your connected account has X Premium or Premium+, set platformSpecificData.longPost to true to publish a single long post (up to 25,000 characters) instead of splitting it into a thread:
{
"content": "A very long post that goes well beyond 280 characters...",
"platforms": [
{
"platform": "twitter",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"longPost": true
}
}
]
}With longPost enabled, PostPeer skips the 280-character pre-check and lets X enforce the real limit for the account. If the account is not eligible for long posts, X rejects it and the error is returned to you. This is a single write request, so it costs the same as a normal tweet (cheaper than a multi-tweet thread).
Platform-Specific Data
Pass this object in platformSpecificData when posting to Twitter/X, including Community post options.
| Field | Type | Required | Values and constraints | Description |
|---|---|---|---|---|
communityId | string | No | — | Community ID to publish this post into. |
shareWithFollowers | boolean | No | — | When posting to a Community, also share the post with followers. |
replyToTweetId | string | No | — | Tweet ID to reply to. |
replySettings | string | No | following, mentionedUsers, subscribers, verified | — |
threadItems | object[] | No | — | Additional tweets to chain as a thread. The root tweet uses post.content. |
threadItems[].content | string | Yes, in each threadItems[] item | — | Text of this thread tweet (max 280 chars) |
threadItems[].mediaItems | object[] | No | — | — |
threadItems[].mediaItems[].type | string | Yes, in each threadItems[].mediaItems[] item | image, video, gif, document | — |
threadItems[].mediaItems[].url | string | Yes, in each threadItems[].mediaItems[] item | Format: uri | — |
threadItems[].mediaItems[].thumbnail | string | No | Format: uri | Thumbnail image URL for video items. Supported on YouTube regular videos (not Shorts). JPEG, PNG, or GIF, max 2 MB, min 640 px wide. |
longPost | boolean | No | — | Opt in to long posts (over 280 characters). Skips PostPeer's 280-character pre-check so X can accept up to 25,000 characters for eligible Premium / Premium+ accounts. If the connected account is not eligible, X rejects the post and the error is surfaced back. No extra API cost. |
poll | object | No | — | Cannot be combined with media or threadItems. |
poll.options | string[] | Yes, when poll is provided | Items: 2–4 | 2–4 poll options. |
poll.durationMinutes | number | Yes, when poll is provided | Range: 5–10080 | Poll duration in minutes (5 min – 7 days). |
Limits
| Limit | Value |
|---|---|
| Character limit | 280 per tweet |
| Images per tweet | 4 |
| Videos per tweet | 1 |
| Thread length | No hard limit |
| Poll options | 2-4 |
Pricing
Most platforms cost a flat 1 credit per post. X is the exception, because X charges PostPeer differently for plain tweets and tweets with links. Rather than spread that markup across every platform's base rate, we keep it isolated to X.
| Action | Credits | Notes |
|---|---|---|
| Post on X (no URL) | 5 | Text, image, video, GIF, poll, thread without links |
| Post on X with a URL | 50 | Body contains http:// or https:// |
| Post on any other platform | 1 | Instagram, LinkedIn, TikTok, YouTube, Pinterest, Facebook, Threads, Bluesky |
| Analytics request | 1 | One credit per call, regardless of how many posts come back |
The split mirrors X's own developer pricing:
| X API operation | X's rate | Why it matters |
|---|---|---|
| Content: Create | $0.015 per request | Plain tweet, no link |
| Content: Create (with URL) | $0.200 per request | Any http(s):// in the body, ~13× the plain rate |
| Posts: Read | $0.005 per resource | Analytics lookups, post fetches |
This is X's pricing, not PostPeer's markup. The full rate sheet lives at docs.x.com/x-api/getting-started/pricing. If you don't need the link to be a real hyperlink, dropping it from the body takes the same tweet from 50 credits down to 5.