Crosspost to X, Bluesky and LinkedIn
Write it once. Fit it to every network.
Crossposting fails on the details: 280 weighted characters on X, 300 graphemes on Bluesky, escaping on LinkedIn, duplicate checks everywhere. We check all of them before anything goes out.
The short answer · Updated Nov 1, 2026
Crossposting means publishing the same post to several social networks. Through our API, list several account ids in one POST /v1/posts, and use platform_options to give a network its own version, for example {"x": {"text": "..."}}. Each account publishes independently, so one network's failure doesn't block the others.
| Network | Text limit | Counted as | Images | Our daily cap per account |
|---|---|---|---|---|
| X | 280 | Weighted characters: URLs 23, emoji and CJK 2 | 4, 5 MB each | 100 |
| Bluesky | 300 | Graphemes (and 3,000 bytes) | 4, 2 MB each | 300 |
| 3,000 | Characters | Up to 20, 20 MB each | 50 |
Text limits from docs.x.com, the Bluesky post lexicon and LinkedIn Help, read 2026-11-01. Image limits and daily caps are what our API enforces.
What does crossposting mean?
Crossposting is publishing one piece of content to several networks or accounts, usually adjusted for each. For a developer it typically means one announcement going to X, Bluesky and LinkedIn at the same moment, from one place in your code.
The adjusting is where it goes wrong. Each network counts length differently, formats text differently, and rejects identical repeats on the same account. A crossposting tool's job is to make those differences visible before you publish, not after one network has posted and another has refused.
Why one text rarely fits every network
Take a 294-character release note with a 34-character link. On Bluesky it is 294 graphemes, under the 300 limit. On X it is 283 weighted characters, because the link counts as 23 but the limit is 280. On LinkedIn it is far under 3,000. One text, three answers.
Emoji and non-Latin text change the math again. Every emoji counts 2 on X and 1 on Bluesky. A Japanese sentence like 新機能: 予約投稿をAPIから取り消せるようになりました。 is 29 graphemes on Bluesky but 53 weighted characters on X, because each Japanese character counts 2 there. The X, Bluesky and LinkedIn limit pages have the full rules.
A worked example: one release note, three networks
This request sends the full note to Bluesky and LinkedIn and a shorter version to X. dry_run: true means nothing is published; we only check it.
curl https://socialsforai.vercel.app/v1/posts \
-H "Authorization: Bearer $SOCIALS_API_KEY" \
-d '{
"accounts": ["acc_bsky_team", "acc_linkedin_cto", "acc_x_company"],
"text": "Release notes for v2.4 are up. Scheduled posts can now be canceled from the API, webhooks retry for 24 hours, and the Bluesky connector supports custom PDS hosts. Everything new, with request examples for curl, TypeScript and Python, is on the changelog page: https://example.com/changelog/v2-4",
"platform_options": {
"x": { "text": "v2.4 is out: cancel scheduled posts from the API, 24h webhook retries, custom Bluesky PDS hosts. https://example.com/changelog/v2-4" }
},
"dry_run": true
}'What does the dry run return?
The preview shows the exact text each account will get (shortened here). ok is true because the X override is 120 weighted characters. The only issue is the x_link_cost warning, which doesn't block. Without the override, the issues list would contain a text_too_long error for acc_x_company: "Text is 283 characters; X allows 280.", with the fix "Shorten it by 3+ characters, or set platform_options.x.text to a shorter version."
{
"object": "preflight",
"ok": true,
"issues": [
{
"account_id": "acc_x_company",
"platform": "x",
"severity": "warning",
"code": "x_link_cost",
"message": "Posts with a URL cost $0.200 through the X API instead of $0.015 (docs.x.com pricing).",
"fix": "Remove the link, or send a link-free version for X in platform_options.x.text. A link in a reply is also billed at the with-URL rate."
}
],
"preview": [
{ "account_id": "acc_bsky_team", "platform": "bluesky", "text": "Release notes for v2.4 are up. ..." },
{ "account_id": "acc_linkedin_cto", "platform": "linkedin", "text": "Release notes for v2.4 are up. ..." },
{ "account_id": "acc_x_company", "platform": "x", "text": "v2.4 is out: cancel scheduled posts ..." }
]
}What happens if one network fails?
The others still publish. Each account is a separate target with its own status, attempts and error. If all succeed, the post is published. If some succeed and others fail permanently, it's partially_published, and the post.partially_published webhook lists which target failed and why.
Retryable failures, such as a rate limit or a 5xx from one network, retry only that target after 30s, 2m, 10m, 30m and 2h. The post's final status is set once every target has finished. To resend only the failed network, create a new post with only that account id; resending all three would trip duplicate_content on the ones that already published.
How does each network handle links, mentions and formatting?
These are differences you don't see in the text itself but that change what readers get.
- X: every URL is wrapped by t.co and counts 23. Each link puts the post in X's $0.200 price tier instead of $0.015.
- Bluesky: links, @mentions and #hashtags become rich-text facets automatically; mentions are resolved to the account's DID. The URL text counts at full length.
- LinkedIn: the text is sent in LinkedIn's "little" text format. We escape the reserved characters
( ) [ ] { } < > @ | * _ ~ \so they show as typed, and leave hashtags intact. An@namefrom X becomes plain text, not a LinkedIn mention. - Images: X allows 4 (5 MB each), Bluesky 4 (2 MB each, per its lexicon), LinkedIn up to 20 (20 MB each). Sending 5 images to an X account fails preflight with
too_many_media.
Common crossposting mistakes and how to fix them
Each of these has an error code, so an agent can fix it without a human.
- One text for all networks:
text_too_longon X. Addplatform_options.x.text. - Reposting to the same account:
duplicate_contentwithin 24 hours. Vary the wording per account or wait. - Video in the media list:
video_not_supportedon every network for now. Use images. - An account that needs reconnecting:
account_needs_reauthon that target only. Send its owner a new connect link; the rest still publish. - Too many accounts on the Free plan:
plan_limit_active_accountsonce a post would make more than 3 accounts active this month. See pricing.
When is a dashboard tool better for crossposting?
If a person writes each post and wants to see it on a calendar, a dashboard scheduler is a better fit; we have no composer or calendar. We're built for crossposting from code: an app that posts on its users' behalf, a release pipeline, or an AI agent using our MCP server. X, Bluesky, LinkedIn, Mastodon and Telegram are live today. See the docs to start.
How to crosspost with one API call
- 01
Connect each account once
Create one connect link per person with the platforms they use. Bluesky asks for an app password, Mastodon for a server URL and access token, Telegram for a bot token and chat; X and LinkedIn use OAuth. You end up with one account id per network.
- 02
Write the longest version as text
Put the full version in text. It goes to every account that has no override.
- 03
Add shorter versions where needed
Set platform_options.x.text (or bluesky, or linkedin) for any network where the main text doesn't fit or shouldn't be identical.
- 04
Dry-run the whole request
Send it with dry_run: true. The response has a preview of the exact text each account will get, and any issue names the account and the fix.
- 05
Publish and read each target
Send it for real. The post is published, partially_published or failed, and each target carries its own URL or error.
Questions
What developers ask
Does a failure on one network stop the others?
No. Each account publishes on its own as a separate target. If some succeed and one fails permanently, the post ends as partially_published, and the failed target carries an error code, message and fix. Retryable failures retry only that target, on a schedule of 30 seconds up to 2 hours.
Can I send different text to each network?
Yes. Put the default in text and per-network versions in platform_options, keyed by platform: x, bluesky or linkedin. Each account gets its platform's override if there is one, otherwise the default. A dry run's preview shows the exact text every account will receive.
Is crossposting the same text penalized?
Posting identical text to the same account twice is what platforms reject, and our preflight blocks it within 24 hours as duplicate_content. Posting the same text once each to different accounts on different networks isn't blocked by our checks. Whether a network ranks it lower is up to that network.
Can I crosspost on a schedule?
Yes. Add scheduled_at to the same request and every account publishes at that time, up to 365 days ahead. Length and duplicate checks run when you create the post, so problems show up immediately rather than at publish time. Cancel with DELETE /v1/posts/:id any time before publishing starts.
Which networks can I crosspost to today?
Five: X, Bluesky, LinkedIn (personal profiles), Mastodon and Telegram, with OAuth or token connect, media upload, retries and verification where the network allows it. Instagram, Threads, Facebook, TikTok, YouTube, Pinterest and Reddit are coming and need platform review or aren't built yet.
Can an AI agent crosspost for me?
Yes. Through our MCP server, an agent in Claude Code, Cursor or another MCP client gets a create_post tool that takes the same accounts, text and platform_options fields. Because every error has a code and a fix, the agent can shorten an X version after a text_too_long and try again.
Get an API key and send your first post in five minutes.