Posts
Publish, media upload, threads, quotes, schedule, and remote delete on X.
Publish to X through the shared POST /v1/posts surface.
Cross-platform flow: Media guide.
Supported formats
mediaKind | Supported | With text | Notes |
|---|---|---|---|
text | Yes | Required | No mediaUrls |
image | Yes | Yes | Single image in mediaUrls (max 5 MB) |
multi-image | Yes | Yes | 2–4 images in mediaUrls |
video | Yes | Yes | Single video (or GIF) URL; not mixed with other media |
Features
| Feature | Supported | Notes |
|---|---|---|
| Thread | Yes | thread[]; media on the first tweet only |
| Quote | Yes | quoteTweetId on create, or POST …/quotes |
| Schedule | Yes | scheduledFor + timezone |
| Remote delete | Yes | Deletes the full thread chain when present |
Create
curl -X POST https://api.onepostly.com/v1/posts \
-H "x-api-key: op_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello from Onepostly",
"mediaKind": "text",
"destinations": [
{ "connectionId": "YOUR_X_CONNECTION_ID" }
]
}'{
"text": "Hello from Onepostly",
"mediaKind": "text", // text | image | multi-image | video — stories not supported
"destinations": [
{ "connectionId": "YOUR_X_CONNECTION_ID" }
]
}X supports text, image, multi-image, and video/GIF (all can include caption text).
Response: 202 with status: "queued". Poll GET /v1/posts/:id until the destination is published or failed — or use webhooks instead of polling.
Rules
mediaKindis required (defaulttextwhen omitted)- Text-only posts require non-empty text and
mediaKind: "text" - Max 280 characters per segment
- Stories (
mediaKind: "stories") are rejected - Wallet: billed at X list prices (create; URL surcharge when applicable)
Schedule
Pass scheduledFor (timezone-naive local datetime, e.g. 2026-07-16T12:00:00) and timezone (IANA, e.g. America/New_York). Defaults to UTC when timezone is omitted. Do not send Z or a numeric offset on scheduledFor. Future times stay scheduled until publish time.
{
"text": "Scheduled hello",
"scheduledFor": "2026-07-16T12:00:00",
"timezone": "America/New_York",
"destinations": [{ "connectionId": "YOUR_X_CONNECTION_ID" }]
}Media
POST /v1/media(multipartfile) → CDNurl- Pass that URL in
mediaUrlson create post with an explicitmediaKind
Limits
| Type | Limit |
|---|---|
| Images | Up to 4 per tweet; max 5 MB each |
| GIF | Single attachment; max 15 MB |
| Video | Single attachment; max 512 MB |
| Mixed multi-video | Rejected |
Media attaches to the first tweet only when publishing a thread.
Thread
{
"thread": ["1/3 intro", "2/3 body", "3/3 CTA"],
"destinations": [{ "connectionId": "YOUR_X_CONNECTION_ID" }]
}thread[0]is the root text (overrides top-leveltextwhen provided)thread[1…]are reply-chain segments- Wallet cost = sum of each segment’s create cost
Quote
Option A — on create
{
"text": "My take",
"destinations": [
{
"connectionId": "YOUR_X_CONNECTION_ID",
"quoteTweetId": "2077337724349026673"
}
]
}Some X API tiers gate quote tweets.
Option B — from an existing published post
curl -X POST https://api.onepostly.com/v1/posts/POST_ID/quotes \
-H "x-api-key: op_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"destinationId":"DEST_ID","text":"My take"}'Creates a new Onepostly post + destination (listable and remote-deletable), quoting the source destination’s externalPostId.
Remote delete
curl -X DELETE \
"https://api.onepostly.com/v1/posts/POST_ID/destinations/DEST_ID" \
-H "x-api-key: op_YOUR_KEY"Thread delete behavior
- Deletes the full thread (replies then root) when applicable
- Already-deleted tweets count as success
- If the root cannot be deleted → fail
- If root is gone but some replies fail →
THREAD_DELETE_PARTIAL
Wallet: billed at X list prices per deleted external id.
Publish errors (X)
| Code | When |
|---|---|
TEXT_REQUIRED / TEXT_TOO_LONG | Empty or >280 chars |
UNSUPPORTED_MEDIA_KIND | Stories |
INVALID_MEDIA / MEDIA_UNFETCHABLE | Bad URL or size/type |
MEDIA_UPLOAD_FAILED / MEDIA_PROCESSING_FAILED | Upload or processing failed |
TOKEN_INVALID | Reconnect account |
PERMISSION_DENIED | Missing tweet.write / media.write |
INSUFFICIENT_WALLET | Onepostly wallet empty |
429 / 502 / 503 | Transient — retry later |
See also
- Overview — scopes and wallet
- Engagement
- Media