Skip to content
SocialsForAI
Menu

Bluesky API

Bluesky is the easiest network to automate. Mostly.

No app review, no paid tier, open protocol. The catches are graphemes, facets, link cards and session limits. Bluesky is one of five networks live on our API today.

The short answer · Updated Nov 1, 2026

Bluesky's API is the AT Protocol: a post is an app.bsky.feed.post record written to the account's repository with com.atproto.repo.createRecord. Text is limited to 300 graphemes (and 3,000 bytes), links and mentions need "facets" with byte offsets, and a post can embed up to 4 images of up to 2 MB each. Bots authenticate with an app password, not the main password.

Bluesky: live. Posting to Bluesky works on the API today.

Bluesky post and account limits
LimitValueSource
Post text300 graphemes and 3,000 bytesapp.bsky.feed.post lexicon
Images per post4app.bsky.embed.images lexicon
Image size2 MB each (formerly 1 MB)app.bsky.embed.images lexicon
Languages per postUp to 3 in langsapp.bsky.feed.post lexicon
Logins (createSession)30 per 5 minutes, 300 per day, per accountBluesky rate limits
Repository writes5,000 points per hour, 35,000 per day; a create costs 3Bluesky rate limits
All API requests3,000 per 5 minutes per IPBluesky rate limits

Lexicons read in @atproto/api; rate limits from Bluesky's docs for hosted (bsky.social) accounts. Read 2026-11-01. Self-hosted PDSes set their own limits.

Is the Bluesky API free?

Yes. Posting to Bluesky through the API has no fee, no paid tier and no app review. You log in as the account, get a session token and write records. The limits are rate limits, counted in points, not dollars.

That makes Bluesky the network where going direct is most reasonable. If Bluesky is the only network you post to, the raw API below is a fine choice. A unified API helps when you post to Bluesky alongside X and LinkedIn, as in crossposting.

How do you post to Bluesky with the API?

Two calls. com.atproto.server.createSession exchanges a handle and app password for an access JWT; com.atproto.repo.createRecord writes the post to the app.bsky.feed.post collection. Each post needs text and createdAt, and the response returns the record's uri and cid.

These are Bluesky's own quick-start examples. $PDSHOST is https://bsky.social for most accounts, or the account's own PDS if it's self-hosted.

bash
curl -X POST $PDSHOST/xrpc/com.atproto.server.createSession \
  -H "Content-Type: application/json" \
  -d '{"identifier": "'"$BLUESKY_HANDLE"'", "password": "'"$BLUESKY_PASSWORD"'"}'

curl -X POST $PDSHOST/xrpc/com.atproto.repo.createRecord \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H "Content-Type: application/json" \
  -d "{\"repo\": \"$BLUESKY_HANDLE\", \"collection\": \"app.bsky.feed.post\", \"record\": {\"text\": \"Hello world! I posted this via the API.\", \"createdAt\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}"

What are the Bluesky API rate limits?

Writes are metered in points per account: creating a record costs 3, updating 2, deleting 1, within 5,000 points an hour and 35,000 a day. That works out to about 1,666 new posts an hour or 11,666 a day, far above anything a well-behaved bot needs.

The limit that bites is logins. createSession allows 30 calls per 5 minutes and 300 per day per account, so a script that logs in before every post will hit it. Store the session and refresh it; we resume a saved session and only log in again with the app password when the session is rejected.

Bluesky's docs say these limits are aimed at bots that follow or like at bulk scale, and that moderation and app-specific limits may apply on top. Our own fair-use cap is 300 posts per Bluesky account per 24 hours.

Why does Bluesky count graphemes, not characters?

Bluesky's lexicon limits post text to 300 graphemes and 3,000 bytes. A grapheme is what a reader sees as one character: a family emoji is several code points but one grapheme, so it counts as 1.

JavaScript's .length counts UTF-16 code units and gets this wrong; Intl.Segmenter gets it right. The 3,000-byte cap rarely matters in English but can in scripts with multi-byte characters or emoji-heavy posts; our preflight checks both limits. Our Bluesky character counter counts the way Bluesky does, and the Bluesky character limit page has the detail.

How do you attach images with the Bluesky API?

Upload each file with com.atproto.repo.uploadBlob, then reference the returned blob in an app.bsky.embed.images embed with alt text and, if you know it, aspectRatio. Bluesky's docs advise leaving aspectRatio out rather than guessing.

The lexicon allows 4 images of up to 2,000,000 bytes each. Bluesky's prose guide still mentions 1 MB in one place while its sample code checks 2 MB; the lexicon is the authority. We download each image from your public URL and return media_too_large if it's over 2 MB.

Should a Bluesky app use app passwords or OAuth?

Bluesky's quick start says apps with end-user login should use OAuth, and that bots and command-line tools can stick with passwords. In every password case it says to use an app password rather than the main account password.

Our connect page uses app passwords today: the account owner creates one in Bluesky's settings, enters it on our hosted page, and we store it encrypted. It can be revoked in Bluesky without changing the main password. Accounts on a self-hosted PDS work too; enter the PDS URL on the connect page.

What errors come back from Bluesky through SocialsForAI?

Each Bluesky failure maps to one of our error codes, with a fix and a retry decision:

  • 401 or AuthenticationRequired: account_needs_reauth. The app password was revoked; create a new one and reconnect with a connect link.
  • 429: platform_rate_limited, retried at 30s, 2m, 10m, 30m and 2h.
  • 400: platform_rejected, not retried; the message carries Bluesky's reason.
  • Other failures: platform_unavailable, retried.
  • Over 300 graphemes or 3,000 UTF-8 bytes: text_too_long at preflight, before any call to Bluesky.

How do you post to Bluesky with one call?

Send text and image URLs to POST /v1/posts. We count graphemes, build facets, upload blobs, write the record, then read it back with getPosts so verified: true in the response means the post exists. For agents, the Bluesky MCP server exposes the same call as a tool.

bash
curl https://socialsforai.vercel.app/v1/posts \
  -H "Authorization: Bearer $SOCIALS_API_KEY" \
  -H "Idempotency-Key: launch-0412" \
  -d '{
    "accounts": ["acc_bluesky", "acc_linkedin", "acc_x"],
    "text": "We just shipped webhooks.",
    "wait": true
  }'

Post to Bluesky through our API

  1. 01

    Get a key

    POST /v1/workspaces returns a Free-plan API key.

  2. 02

    Create a connect link

    POST /v1/connect-links with platforms set to bluesky, and send the URL to the account owner.

  3. 03

    Owner adds an app password

    They create an app password in Bluesky's settings and paste it with their handle on the connect page.

  4. 04

    Publish

    POST /v1/posts with the new account id. The response includes the bsky.app URL and verified set to true once the post is read back.

Questions

What developers ask

Is the Bluesky API free?

Yes. There's no paid tier and no app review for posting from your own or a consenting user's account. Usage is governed by rate limits instead: 5,000 write points an hour and 35,000 a day per account on Bluesky's hosted service, with a post costing 3 points.

Does Bluesky have an API for posting?

Yes. Bluesky runs on the AT Protocol, and posting is a call to com.atproto.repo.createRecord that writes an app.bsky.feed.post record after logging in with com.atproto.server.createSession. Bluesky's quick start shows the same two calls in TypeScript, Python and plain curl, with no app registration needed.

What is a Bluesky app password?

A separate password created in Bluesky's settings for one app or bot. It logs in like the main password but can be revoked on its own. Bluesky's docs say to use an app password rather than the main password for API access.

Why aren't my links clickable on Bluesky?

Bluesky needs a facet for each link: a byte range in the UTF-8 text plus the link URI. Text alone stays plain. Posts sent through our API get link, mention and hashtag facets automatically. Link preview cards are a separate embed we don't generate yet.

How many posts can a Bluesky bot make per day?

Bluesky's write limit allows about 11,666 new records per day per account, at 3 points each within 35,000 daily points. The tighter limit is logins: 300 createSession calls per day, so reuse sessions. Our fair-use cap is 300 posts per account per day.

Can I post to a self-hosted Bluesky PDS?

Yes. The AT Protocol lets accounts live on any PDS, and the same createSession and createRecord calls work against that host. On our connect page, enter the PDS URL alongside the handle and app password; leave it blank for bsky.social.

Get an API key and send your first post in five minutes.