Last updated: September 2026
Is there one API endpoint that posts to X and Bluesky at once?
Yes. POST /api/v1/posts with platforms: ["x","bluesky"] publishes a native post on each network from one request, and returns a result per network so you know what happened on each.
One thing to get right before you ship: omitting the platforms field does not mean X. Below is the parameter, the response shape, and the two calls worth making before your first real post.
The call
One bearer key, one endpoint. Each network is delivered separately through its own API, so what lands on Bluesky is a Bluesky post and not a link back to X.
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{
"text": "Shipped cross-posting today.",
"platforms": ["x", "bluesky"],
"publish_now": true
}'Swap publish_now for scheduled_date and the same rules apply at publish time. In a bulk create, platforms can be set per post or once at the top level for the whole batch.
The four cases, including the one that catches people
Omitting platforms is not the same as pinning to X
A post with no platforms field follows the account auto cross-post setting, and when that setting is on it goes to every connected network. If an existing integration has been writing X-only posts by leaving the field out, turning auto cross-post on starts sending those posts to Bluesky as well. Pass an explicit value and this stops being a question.Read the outcome per network
The response carries a results array, one entry per network, so a partial outcome is visible rather than hidden behind a single flag.
{
"results": [
{
"platform": "x",
"status": "published",
"post_id": "1834000000000000001",
"url": "https://x.com/you/status/1834000000000000001"
},
{
"platform": "bluesky",
"status": "skipped",
"skip_reason": "Text is 412 characters. Bluesky allows 300."
}
]
}A post that does not fit one network is skipped there with the reason recorded, and still goes out everywhere it fits. That is deliberate: the alternative is failing the whole call, or truncating your words without asking.
Check first, then post
GET /api/v1/cross-posting returns the auto cross-post setting, the deliverable targets, the connected accounts, and the per-network limits. Call it once at startup so your code knows the rules it is writing against.
Worth being clear about what this is not. OpenTweet is X-native and goes deep on X, with analytics, evergreen recycling, DM campaigns and Articles. Bluesky is a mirror for posts you are already writing, which is why the Bluesky column above has honest gaps in it. LinkedIn and Threads are on the roadmap and are not shipped. Full parameter reference in the cross-posting docs.
7-day free trial. Cancel anytime.
Frequently asked questions
Is there one API endpoint that posts to X and Bluesky at once?
Yes. POST https://opentweet.io/api/v1/posts takes a platforms array, and platforms: ["x","bluesky"] publishes a native post on each network from one request. It is not a link from one to the other, and it is not a repost. Each network gets its own real post through its own API.
What happens if I leave the platforms field out?
It does not mean X. A post with no platforms field follows the account auto cross-post setting, and when that setting is on it goes to every connected network. An explicit platforms value is always honoured exactly and never expanded, so automation that must stay X-only should pass ["x"] rather than omitting the field.
What happens if the post fits one network and not the other?
It publishes where it fits and is skipped where it does not, with the reason recorded against the post. Bluesky allows 300 characters and X allows 280, or 25,000 on Premium, so a 400-character post publishes on a Premium X account and comes back as skipped on Bluesky with a stated reason. Nothing is silently truncated and the whole call does not fail.
How do I know where a post will actually land before I send it?
Call GET /api/v1/cross-posting. It returns the auto cross-post setting, the deliverable targets, which accounts are connected on each network, and the per-network limits including the character count and image rules. Read that once at startup rather than guessing.
Does the response tell me what happened on each network?
Yes. The post carries a results array with one entry per network, each with a status of published, failed or skipped, plus the platform post id and url on success and a skip_reason when it was skipped. You do not have to infer the outcome from a single overall flag.
Do I need an X developer account or a Bluesky API key for this?
Neither. OpenTweet holds the X connection for you, so there is no developer app to register and no pay-per-use meter, and Bluesky has no API keys to issue in the first place. One ot_ bearer key covers both, on a flat $11.99 a month.
Keep exploring
One endpoint, one key, and a result you can read per network.
Cross-posting API reference
The platforms parameter, the results array, and the auto cross-post setting in full.
The same thing over MCP
An MCP server whose posting tools take the same platforms array.
What does it cost?
The Bluesky half is zero. The whole bill is the X half, with worked monthly math.
Is there a Bluesky API key?
No, and nothing to apply for. What you authorize instead of a key.
API or MCP for an agent?
When a REST endpoint is the right call and when MCP is.
Cross-posting to X and Bluesky
One draft, a native post on each network, and a per-network result you can read.
One POST, both networks
No X developer account, no Bluesky API key, no per-post fee. Flat $11.99 a month.
7-day free trial. Cancel anytime.