The Bluesky posting APIin one REST call
The Bluesky API (AT Protocol) is free and needs no API key: you log in as an account and write records. OpenTweet's Bluesky posting API wraps it in one REST call, POST /api/v1/posts with platforms ["bluesky"], and you connect accounts through Bluesky OAuth, so no app password is stored.
Last updated: October 2026
7-day free trial. Cancel anytime.
What the Bluesky API gives you for free
No key, no developer account, no paid tier. Also no queue, no facets and no retries.
Bluesky runs on the AT Protocol. A post is a record of type app.bsky.feed.post written to the account's repository, and any code that is logged in as that account can write one. There is nothing to apply for and nothing to pay. Why there is no API key.
The limits are rate limits per account: 5,000 write points an hour and 35,000 a day, with a new post costing 3 points. What that means in posts per day.
What the raw API leaves to you: a URL in the text is not clickable unless the record carries a facet with its byte range, images must fit 2,000,000 bytes each, a thread needs the root and parent reference on every reply, and there is no field that delays a post. Scheduling means running your own cron or queue.
Raw atproto vs the OpenTweet Bluesky API
Both write the same record to Bluesky. The difference is how much code sits around it.
| Raw atproto (@atproto/api) | OpenTweet | |
|---|---|---|
| Price | Free | From $11.99 a month, Bluesky on every plan |
| API key | None. You log in as the account | One ot_ key for every network |
| Auth | An app password in your environment, or atproto OAuth you build and host yourself | Bluesky OAuth once in the dashboard, scoped to writing posts and uploading images |
| Clickable links and mentions | You compute facets, for example with RichText.detectFacets | Facets added for you. A mention that does not resolve stays plain text |
| Images | uploadBlob, then an images embed. You keep each under 2,000,000 bytes | media_urls, up to 4, resized to fit 2,000,000 bytes |
| Threads | Set reply root and parent on every post | is_thread + thread_tweets, up to 25 posts per call |
| Scheduling | Your own cron or queue | scheduled_date on the same call |
| Retries | Your code | Retried only when nothing reached Bluesky, so a half-posted thread is never posted twice |
| Several accounts | One session per account | 1 / 3 / 10 by plan, picked with bluesky_account_id |
| Same post to X and LinkedIn | Separate integrations | Add them to platforms |
| Reading feeds, replies, search | Yes | No. Publishing only |
OpenTweet limits come from its Bluesky adapter: 300 graphemes and 3,000 bytes of text, up to 4 images. Per-network limits in full.
Create a Bluesky post with the API
Connect your Bluesky account once in the dashboard, create an API key, then call POST /api/v1/posts. Use scheduled_date to queue it or "publish_now": true to post now.
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{
"text": "Release notes are up: https://example.com/changelog",
"platforms": ["bluesky"],
"scheduled_date": "2026-10-20T14:00:00Z"
}'const res = await fetch('https://opentweet.io/api/v1/posts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OPENTWEET_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: 'Release notes are up: https://example.com/changelog',
platforms: ['bluesky'],
publish_now: true,
media_urls: ['https://example.com/chart.png'],
}),
})
console.log(await res.json())# pip install requests
import os
import requests
r = requests.post(
"https://opentweet.io/api/v1/posts",
headers={"Authorization": f"Bearer {os.environ['OPENTWEET_API_KEY']}"},
json={
"text": "What changed in v2.4, in three posts.",
"is_thread": True,
"thread_tweets": [
"One. Exports are faster on large workspaces.",
"Two. The API returns per-network results now.",
],
"platforms": ["bluesky", "x", "linkedin"],
"scheduled_date": "2026-10-20T14:00:00Z",
},
)
print(r.status_code, r.json())Every network the post reached comes back in results, with the Bluesky AT URI, CID and a bsky.app link:
"results": [
{
"platform": "bluesky",
"status": "published",
"post_id": "3l5x7k2qz3c2t",
"uri": "at://did:plc:abc123/app.bsky.feed.post/3l5x7k2qz3c2t",
"cid": "bafyreib2rxk3rh6kzwq...",
"url": "https://bsky.app/profile/you.bsky.social/post/3l5x7k2qz3c2t"
}
]With more than one Bluesky account connected, pick one with bluesky_account_id. Leave it out and the primary account is used. GET /api/v1/cross-posting lists what is connected. Name platforms on every call from an unattended script, because a post with no platforms follows the account's auto cross-post setting. Full API reference.
The same post with @atproto/api, for comparison
The official TypeScript SDK. This is the version OpenTweet runs; the latest on npm in October 2026 is 0.24.3.
// npm install @atproto/api (written against 0.20.42)
import { AtpAgent, RichText } from '@atproto/api'
const agent = new AtpAgent({ service: 'https://bsky.social' })
await agent.login({
identifier: 'yourname.bsky.social',
password: process.env.BLUESKY_APP_PASSWORD,
})
// Without facets, the URL below is plain text, not a link
const rt = new RichText({ text: 'Release notes are up: https://example.com/changelog' })
await rt.detectFacets(agent)
await agent.post({
text: rt.text,
facets: rt.facets,
createdAt: new Date().toISOString(),
})That is the whole happy path for one text post on one account. Images add an uploadBlob call per file, threads add reply references, and scheduling adds a process that is awake at the right minute. Log in once and reuse the session: session creation is capped at 30 per 5 minutes and 300 a day per account.
To see exactly what detectFacets produces for your text, paste it into the Bluesky facet generator. For the Python SDK and a bot that runs on a schedule, see post to Bluesky with Python and build a Bluesky bot.
What the OpenTweet layer does
The parts of a Bluesky integration that are easy to get wrong, handled before the record is written.
No app password
Accounts connect through Bluesky OAuth with a write-only scope for posts and image uploads. OpenTweet never sees or stores the account password or an app password.
Links that work
Link, mention and hashtag facets are computed for every post, so a URL is clickable on Bluesky instead of arriving as plain text.
Images that fit
Bluesky caps each image at 2,000,000 bytes, well below X. Images over that are resized before upload, so the same media_urls work on both.
A real queue
scheduled_date puts the post in a queue that publishes on time, shows on the calendar, and stays editable with PUT /api/v1/posts/{id}.
Need X too? One endpoint posts to X and Bluesky at once, and LinkedIn joins with one more entry in platforms.
For AI agents: the Bluesky MCP server
If the agent runs in Claude, Cursor, ChatGPT or another MCP client, skip the HTTP code. The hosted OpenTweet MCP server exposes 43 tools on the same account.
opentweet_create_tweet({
text: "Release notes are up: https://example.com/changelog",
platforms: ["bluesky"],
scheduled_date: "2026-10-20T14:00:00Z"
})Setup for each client, and why OAuth beats an app password in an agent config, is on the Bluesky MCP server page.
Honest limits
What the OpenTweet Bluesky API does not do, as of October 2026.
Not supported on Bluesky
- Video. A post with a video is skipped on Bluesky with a reason
- Link cards. Links are clickable, but no preview card is attached
- Bluesky analytics. There are no Bluesky stats in OpenTweet
- Auto-splitting. Text over 300 graphemes is skipped, not split into a thread
- Reading. No feeds, replies, search or follower data
Plan limits that apply
- Posts a day: 20 on Pro, 100 on Advanced, 300 on Agency
- Posts with a link a day: 10 on Advanced, 20 on Agency. Pro needs a link credit pack
- Over the link allowance with no credits, links are removed before posting
- Threads sent through the API: up to 25 posts
When to skip OpenTweet
If you need Bluesky video, link cards, feeds or analytics, or you only ever post from one account and are happy running your own cron, call the Bluesky API directly. It is free.Bluesky API pricing through OpenTweet
Bluesky charges nothing. OpenTweet is a monthly plan with the API, MCP server and CLI included.
Pro
$11.99 / month
1 Bluesky account, plus the same number of X and LinkedIn accounts.
Advanced
$29 / month
3 Bluesky accounts, plus the same number of X and LinkedIn accounts.
Agency
$49 / month
10 Bluesky accounts, plus the same number of X and LinkedIn accounts.
No per-post fee. Every plan starts with a 7-day free trial. Compare plans.
Frequently asked questions
Is the Bluesky API free?
Yes. Bluesky has no API key, no developer account and no paid tier. Posting is writing a record to the account’s repository, and that costs nothing. Your costs are hosting your own code and staying inside the per-account rate limits: 5,000 write points an hour and 35,000 a day, where a new post costs 3 points.
Do I need a Bluesky API key?
No. There is nothing to apply for. A script logs in as the account, usually with an app password, and an app can use atproto OAuth, where the client_id is the URL of a metadata document you host. OpenTweet uses OAuth, so you sign in to Bluesky once in the dashboard and your code holds only an OpenTweet ot_ key.
How do I create a post with the Bluesky API?
With @atproto/api, log in with AtpAgent, run RichText.detectFacets on your text so links and mentions are clickable, then call agent.post with the text, the facets and a createdAt timestamp. Through OpenTweet, send POST https://opentweet.io/api/v1/posts with your text and "platforms": ["bluesky"], plus scheduled_date or "publish_now": true.
Can I schedule Bluesky posts through an API?
Not with the Bluesky API itself: creating a record publishes it immediately, and there is no field that delays it. You run your own cron or queue. OpenTweet takes a scheduled_date in ISO 8601 on the same call and publishes at that time, and the post stays editable with PUT /api/v1/posts/{id} until it goes out.
Can one API call post to Bluesky, X and LinkedIn?
Yes. Put "bluesky", "x" and "linkedin" in the platforms array. Each network gets its own native post and reports its own outcome in the results array. A post that breaks one network’s rules, such as going over 300 graphemes on Bluesky, is skipped there with a reason and still goes out on the others.
Does the OpenTweet Bluesky API support video or link cards?
No. A post with a video goes out on X but is skipped on Bluesky with a reason. Links are clickable through facets, but no link card (the app.bsky.embed.external embed) is attached. OpenTweet also has no Bluesky analytics. If you need those, call the Bluesky API directly.
How much does the OpenTweet Bluesky API cost?
The API is included on every plan. Pro is $11.99 a month with 1 Bluesky account, Advanced is $29 with 3, and Agency is $49 with 10. Each plan includes the same number of X and LinkedIn accounts, and there is no per-post fee.
More on Bluesky API access: posting without an API key and how many posts a day the rate limits allow.
Keep exploring
Bluesky guides, tools and the API reference.
Post to Bluesky with Python
The atproto SDK and the one-call route side by side: login, link facets, image blobs and threads.
Build a Bluesky bot
A scheduled poster from GitHub Actions, the RSS version, and the rate limits it runs into.
One API endpoint for X and Bluesky
How one request reaches both networks, and what each one reports back.
Bluesky facet generator
Paste a post and get the facets JSON with byte offsets for links, mentions and hashtags.
Bluesky MCP server
The same posting through MCP tools for Claude, Cursor and other agents, with OAuth instead of an app password.
Bluesky scheduler
Schedule Bluesky posts and threads from the dashboard, in the same queue the API writes to.
REST API reference
Every endpoint, every field of POST /api/v1/posts, and the response shape.
Cross-posting docs
The platforms field, per-network results, and how to pick a specific connected account.
Social media API for AI agents
Direct network APIs, unified APIs and MCP servers compared for agent use.
Post to Bluesky with one API call
Bluesky OAuth, scheduling, clickable links and cross-posting to X and LinkedIn, on one API key. From $11.99 a month.