Quick reference
| Limit / capability | Value |
|---|---|
| Character limit | 280 graphemes (t.co-weighted) |
| Images per tweet | 4 |
| Image formats | jpeg, png, webp |
| Image size | 5,000,000 bytes (5 MB) |
| GIF size | 15,000,000 bytes (15 MB) |
| Video formats | mp4 (H.264 + AAC recommended) |
| Video size | 512,000,000 bytes (512 MB) |
| Video duration | 140 s on the standard tier (platform-enforced) |
| Mixed image + video | Rejected — pick one per tweet |
| Alt text per item | 1,000 graphemes (best-effort write) |
| Post types supported | text, single image, multi-image, single video, single GIF, reply, quote |
| Scheduling | Yes (via scheduledAt) |
| Thread (reply chain) | Yes (twitter.replyToTweetId) |
| Quote tweet | Yes (twitter.quoteTweetId) |
| First comment | Not supported (use a reply chain) |
| Inbox / DM | Not supported in v1 |
| Analytics | Not supported in v1 |
Connect an account
POST /v1/accounts/connect/twitter. OAuth 2.0 PKCE. letmepost signs a state token carrying the PKCE code_verifier through the redirect so the dashboard never stashes it client-side. After the X callback, the code+verifier exchange runs server-side.
Scopes
like.read, follows.read — off by default; not needed for publishing.
Token lifecycle
X access tokens are short-lived (~2 hours);offline.access mints a refresh token that lets the publisher refresh on schedule. If a user revokes app access from X’s settings the next call surfaces platform_auth_failed.
Post types
Text post
text.json
Single image
single-image.json
Multi-image (up to 4)
multi-image.json
Single video
Chunked upload runs transparently — small clips return immediately, larger ones runINIT → APPEND → FINALIZE → STATUS until X reports succeeded.
video.json
Reply (and threads)
Threads on X are reply chains — pass the previous tweet id on each follow-up. The first tweet in a thread is just a normal post; the second carriesreplyToTweetId set to the first’s id, the third to the second’s, and so on.
reply.json
Quote tweet
Mutually exclusive withreplyToTweetId — preflight rejects setting both, since X itself does.
quote.json
Wisdom (platform-specific things that bite)
Common errors
| Error rule | What it means | How to fix |
|---|---|---|
twitter.text.max_graphemes | Weighted character count > 280 | Trim text or shorten URLs (won’t help past 23 chars/URL). |
twitter.text.non_empty | Tweet body was empty | Provide non-whitespace text — X requires it even on media tweets. |
twitter.media.count_max | > 4 images, or > 1 video/GIF | Up to 4 images, 1 video, or 1 GIF per tweet. |
twitter.media.image_video_exclusive | Mixed image + video | Pick one media kind per tweet. |
twitter.media.image_size_max | Image > 5 MB | Re-encode under 5,000,000 bytes. |
twitter.media.gif_size_max | GIF > 15 MB | Re-encode under 15,000,000 bytes. |
twitter.media.video_size_max | Video > 512 MB | Compress under 512,000,000 bytes. |
twitter.media.video_processing_failed | FINALIZE / STATUS returned an error | Inspect platformResponse.processing_info.error for the codec / container reason. |
twitter.media.processing_timeout | STATUS poll exceeded the deadline | Retry; X’s transcoder sometimes lags. |
twitter.launch_cap.per_account | Per-account launch-cap exhausted | Wait for the cap window to roll over, or upgrade the X tier. |
What you can’t do (yet)
- Polls.
- Long-form (premium) tweets above 280 graphemes — the preflight cap is wired for the standard limit; long-form support lands in a follow-up slice.
- Reading replies, likes, or retweet metadata.
- DMs.
- Lists (creating tweets that target a list audience).
- Spaces.
API reference
POST /v1/posts— primary publish.POST /v1/media— upload images/videos formediaIdreferences.POST /v1/accounts/connect/twitter— start a Twitter OAuth flow.

