Create a thread
Pass a parts array (1–25 items) to publish a thread. Each part has a caption and an
optional media array (up to 4 items). Threads are supported on X and Mastodon;
other platforms ignore parts.
The two platforms treat the top-level caption differently:
- X — when
partsis present, the whole thread is built fromparts:parts[0]is the first tweet and each later part replies to the previous one. Setcaptionto the same text asparts[0](it's still required, and is what listings show). The top-levelcaptionandmediaaren't posted, so put the first tweet's media onparts[0].media. - Mastodon —
caption(andmedia) is the first toot, and each part is posted as a reply under it.
The target account must be connected: creating a thread for a disconnected account is
rejected up front with 400 (reconnect it first), rather than failing at publish time.
curl -X POST https://api.schedulin.app/v0/posts \ -H "x-api-key: $SCHEDULIN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "socialAccountId": "acc_456", "caption": "We rebuilt our scheduler from scratch. Here is what we learned 🧵", "parts": [ { "caption": "We rebuilt our scheduler from scratch. Here is what we learned 🧵" }, { "caption": "1. Queues beat cron for anything user-facing." }, { "caption": "2. Measure before you optimize. This chart surprised us:", "media": [{ "url": "https://www.schedulin.app/schedulin-analytics-card.png" }] } ], "action": "schedule", "scheduledAt": "2030-01-15T16:00:00Z" }'
const intro = "We rebuilt our scheduler from scratch. Here is what we learned 🧵"; await client.posts.create({ socialAccountId: "acc_456", caption: intro, parts: [ { caption: intro }, { caption: "1. Queues beat cron for anything user-facing." }, { caption: "2. Measure before you optimize. This chart surprised us:", media: [{ url: "https://www.schedulin.app/schedulin-analytics-card.png" }], }, ], action: "schedule", scheduledAt: "2030-01-15T16:00:00Z", });
from datetime import datetime, timezone intro = "We rebuilt our scheduler from scratch. Here is what we learned 🧵" client.posts.create( social_account_id="acc_456", caption=intro, parts=[ {"caption": intro}, {"caption": "1. Queues beat cron for anything user-facing."}, { "caption": "2. Measure before you optimize. This chart surprised us:", "media": [{"url": "https://www.schedulin.app/schedulin-analytics-card.png"}], }, ], action="schedule", scheduled_at=datetime(2030, 1, 15, 16, 0, tzinfo=timezone.utc), )
Edit a thread
GET /v0/posts/{id} returns the thread as parts (in order, each with caption and
media; empty for posts that aren't threads). To change it, send parts to
PUT /v0/posts/{id} — it replaces the whole thread, and [] removes it (other platforms
ignore parts, as on create). Part media takes
the same items as the post's media (a URL or a media library { "id" }), so the parts
from a GET can be sent back as-is. Published, publishing, and failed posts can't be edited
(409).
On X, parts[0] stays in step with caption: sending parts without caption sets the
caption to parts[0], and changing only caption also updates parts[0] when the two
matched. Mastodon's caption is its own toot, so its parts change only when you send
parts.
curl -X PUT https://api.schedulin.app/v0/posts/post_123 \ -H "x-api-key: $SCHEDULIN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "parts": [ { "caption": "We rebuilt our scheduler. Here is what we learned 🧵" }, { "caption": "1. Queues beat cron for anything user-facing." } ] }'