Bluesky

Publish posts, threads, replies, images, videos and link cards to Bluesky (AT Protocol), and delete them, through the Postgale API and MCP server.

Postgale publishes to Bluesky accounts: text posts with links, mentions and hashtags, up to 4 images or one video, link cards, threads and replies, and deletes of what was published. Everything is available over the REST API (paths relative to https://postgale.com/api/v1) and the MCP server (connect_account, create_post, manage_published_post). Each connected Bluesky account has platform bluesky; its platformAccountId is the account's DID (did:plc:…).

Connect an account

Bluesky accounts connect with their handle and an app password. Postgale never needs the account password.

  1. In Bluesky, open Settings → Privacy and security → App passwords and choose Add App Password. Name it (e.g. Postgale), leave direct-message access off, and copy the password: it looks like xxxx-xxxx-xxxx-xxxx.
  2. In Postgale open Dashboard → Accounts → Bluesky, enter the handle (e.g. yourname.bsky.social or your own domain) and paste the app password.

Or connect from your code (the answer is the connected account; account.connected is sent as well):

POST /api/v1/connect/bluesky
curl -X POST https://postgale.com/api/v1/connect/bluesky \
  -H 'Authorization: Bearer sk_…' \
  -H 'Content-Type: application/json' \
  -d '{"handle":"yourname.bsky.social","appPassword":"xxxx-xxxx-xxxx-xxxx","externalId":"customer-42"}'

# {"platform":"bluesky","account":{"id":"…","platform":"bluesky","accountName":"@yourname.bsky.social","platformAccountId":"did:plc:…","externalId":"customer-42"}}

Postgale resolves the handle to its DID and personal data server (PDS), works on any PDS (bsky.social or self-hosted), and signs in there. The session tokens and the app password are stored encrypted; sessions are renewed automatically, and if the renewal fails Postgale signs in again with the app password. A wrong handle or password answers 400 with the reason; anything other than an app password is refused.

If you delete the app password in Bluesky (or change the account password), the next call fails: the account becomes needs_reconnect and account.disconnected is sent (reason token_invalid). Connect it again with a new app password; the same account is updated, its post history stays.

Publish

The post content is the text, up to 300 characters as Bluesky counts them (graphemes: an emoji with modifiers counts once). Links, @handle mentions and #hashtags in the text become clickable: mentions are resolved to the person's DID and left as plain text when the handle does not exist. Without media, the first link gets a link card (title, description and image from the page) when the page can be read; a card that cannot be built never fails the post.

POST /api/v1/posts
curl -X POST https://postgale.com/api/v1/posts \
  -H 'Authorization: Bearer sk_…' \
  -H 'Content-Type: application/json' \
  -d '{"content":"Our spring release is out 🌱 https://example.com/blog/spring #release","platforms":[{"accountId":"acc_bsky","platformSpecificData":{"mediaUrls":["https://media.postgale.com/…/hero.jpg"],"altTexts":["The new dashboard in dark mode"],"langs":["en"]}}],"publishNow":true}'
platformSpecificDataWhat it does
mediaUrl / mediaUrlsUp to 4 images (JPEG, PNG, WebP or GIF, each ≤1,000,000 bytes) or one MP4 video (≤100 MB, ≤3 minutes). Images and a video cannot be mixed. Public https URLs, e.g. an uploaded media publicUrl
altText / altTextsAlt text for the image(s) or the video (≤2000 characters each)
threadItemsA thread: up to 25 posts of {content, mediaUrl?, mediaUrls?, altText?, altTexts?}, each a reply to the previous one. Replaces the post text
replyToReply to an existing post: its at:// URI or a https://bsky.app/profile/<handle>/post/<id> link
langsUp to 3 language tags, e.g. ["en"]
disableLinkPreviewNo link card

Limits are checked when the post is created; file sizes, types and the video length are checked again when the files are fetched, before anything is posted. The target's platformPostId is the post's at:// URI and url its https://bsky.app/profile/<handle>/post/<id> link. For a thread they are those of the first post; if a later post of the thread fails, the thread stops there and the target gets a warning (the published posts are not repeated).

Delete

MethodPathWhat it does
POST/posts/{id}/unpublishDelete the post from Bluesky (for a thread: the first post)
GET/accounts/{id}/healthWhether the session still works

Deletes are queued for a person while approval applies to the API key, the organization or the account (202 pending_approval; MCP manage_published_post returns the queued action). Bluesky posts cannot be edited.

Bluesky's rules to know

  • Rate limits: when Bluesky answers 429, Postgale retries after the reset time Bluesky gives.
  • Revoked app passwords: an app password Bluesky refuses marks the account needs_reconnect (reconnect_required on the API) and the post is not retried automatically.
  • Analytics: likes, reposts and views are not collected yet, so post and account analytics answer not_supported.

On this page