A text post needs no media, so you can send it as JSON (or as form fields, if that is easier). title is the text of the post; name the profile in user and the networks in platform.
A first text post
curl -X POST https://api.reelspan.com/v1/posts/text \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: launch-note-2026-12-01" \
-d '{
"user": "my-brand",
"platform": ["x", "threads", "bluesky", "linkedin"],
"title": "We just shipped scheduled reports. Every Monday, in your inbox."
}'import { Reelspan } from 'reelspan';
const reelspan = new Reelspan();
const post = await reelspan.posts.text({
user: 'my-brand',
platforms: ['x', 'threads', 'bluesky', 'linkedin'],
title: 'We just shipped scheduled reports. Every Monday, in your inbox.',
});from reelspan import Reelspan
reelspan = Reelspan()
post = reelspan.posts.text("my-brand", ["x", "threads", "bluesky", "linkedin"], "We just shipped scheduled reports. Every Monday, in your inbox.")In JSON, platform is an array ("platform[]" works as the key too). As a form, repeat -F "platform[]=x". A post without title gets 400 TITLE_REQUIRED.
Which networks take text
X, Threads, Bluesky, LinkedIn, Facebook, Reddit, Google Business, Telegram, Discord, Slack, Mastodon, Nostr, Lemmy, Dev.to, Hashnode, WordPress, Whop and Listmonk.
TikTok, Instagram, YouTube and Pinterest have no text posts: naming one gets 400 UNSUPPORTED_POST_TYPE, and nothing is posted.
Long text and threads
On X a text longer than 280 characters is split into a thread, and on Bluesky one longer than 300; each later post replies to the one before. The result carries a warning saying how many posts the thread has. On X, send x_long_text_as_post=true to keep it as one long post instead (this needs an X account that can post long text).
Links are removed from X posts unless you send
x_keep_links=true, because X charges for posts with a link. Keeping links uses a monthly allowance that depends on your plan (Free and Starter have none); past it the links are removed and the result says so.
Title, description and per-network text
title is the text everywhere. To say something different on one network, send {network}_title (x_title, linkedin_title, bluesky_title…).
Some networks also read description (or {network}_description):
| Network | Uses title for |
Uses description for |
|---|---|---|
| LinkedIn, Facebook | the post, when there is no description | the post text, when given |
the post title (its first line, up to 300 characters; or reddit_title) |
the body (reddit_description; else the caption) |
|
| Dev.to, Hashnode, WordPress | the article title ({network}_title, else the caption's first line) |
the article body (else the caption) |
| Lemmy, Listmonk | the title | the body |
On LinkedIn and Facebook a
descriptionwins overtitle. Send onlytitlefor a plain post there, or setlinkedin_description/facebook_descriptionwhen you mean to.
Network settings
| Network | Field | What it does |
|---|---|---|
facebook_link |
A link Facebook shows as a preview card. | |
reddit_subreddit |
The community to post in. Required. | |
reddit_flair_id, reddit_nsfw, reddit_spoiler |
Flair and labels. | |
| X | reply_settings, reply_to_id |
Who can reply; post as a reply. |
| Threads | threads_reply_control, threads_topic_tag |
Who can reply; a topic tag. |
visibility |
PUBLIC, CONNECTIONS or LOGGED_IN. |
|
| Mastodon | mastodon_visibility, mastodon_spoiler_text |
Visibility; a content warning. |
| WordPress | wordpress_status |
Publish or keep as a draft. |
Each network's page under Networks lists everything it takes and the values each field allows.
First comment
first_comment is posted right after the post goes live: as a reply on X, Bluesky and Threads, as a comment elsewhere. {network}_first_comment overrides it for one network. If a network can't take it, the post still counts and its result carries a warning.
The response
Text posts usually finish well inside the request, so the answer is 200 with results, one per network:
{
"success": true,
"request_id": "4c7d1e9a0b2f4e6c8a1d3f5b7e9c0a2d",
"results": {
"x": { "success": true, "url": "https://x.com/mybrand/status/18…", "post_id": "18…" },
"linkedin": { "success": true, "url": "https://www.linkedin.com/feed/update/urn:li:share:72…", "post_id": "urn:li:share:72…" }
},
"usage": { "count": 12, "limit": null, "last_reset": "2026-11-01T00:00:00.000Z" }
}If one takes longer than 59 seconds, the answer has a message and the request_id instead, and you follow it with Post status or a webhook.
Later, queued or repeated
- At a time:
scheduled_date(ISO 8601) andtimezone; the answer is 202 with ajob_id. - In the queue:
add_to_queue: truetakes the profile's next free slot; see Queue. - On repeat:
repeat_every(day,weekormonth) withrepeat_times(2 to 30); see Scheduling and repeats.
await reelspan.posts.text({
user: 'my-brand',
platforms: ['x', 'linkedin'],
title: 'Tip of the week: schedule once, post everywhere.',
scheduledDate: '2026-12-07T09:00:00',
timezone: 'Europe/London',
repeatEvery: 'week',
repeatTimes: 6,
});reelspan.posts.text(
"my-brand", ["x", "linkedin"], "Tip of the week: schedule once, post everywhere.",
scheduled_date="2026-12-07T09:00:00", timezone="Europe/London", repeat_every="week", repeat_times=6,
)