Platforms

Bluesky

Connect Bluesky with an app password, publish text, image, video and thread posts, tag the post language, and answer DMs and comments.

Bluesky connects with an app password instead of OAuth, then behaves like any other account: you publish with POST /v1/posts using the platform id bluesky, and the same account shows up in the Inbox for DMs and comments.

#What you can do

Feature Supported Where
Text, image and video posts Yes Post types
Threads Yes, with threadItems Threads
Post language tags Yes, per post or as an account default Post language
Scheduling Yes Scheduling
Editing a published post No Bluesky has no post editing
DMs Text only Inbox
Comments List, reply, delete, like Inbox
Analytics Likes, comments, reposts Analytics

#Connect

The user creates an app password in Bluesky (Settings, Privacy and security, App passwords) and gives you their handle. Custom-domain handles such as brand.com work as well as brand.bsky.social. An app password looks like xxxx-xxxx-xxxx-xxxx and never exposes the main account password. The user can revoke it in Bluesky at any time.

Send both to Connect Bluesky account. state is {userId}-{profileId}, where userId is your user id from GET /v1/users and profileId is the profile that should own the account. Sending only the profile id also works.

curl -X POST https://api.pozy.ai/v1/connect/bluesky/credentials \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "acme.bsky.social",
    "appPassword": "abcd-efgh-ijkl-mnop",
    "state": "66a0e8b1c2d3e4f5a6b7c8d9-66a1f0c2a4b9d3e8f1a2b3c4"
  }'
Warning

Collect the app password over HTTPS and send it straight to Pozy. Do not store it yourself. If the user pastes their main password instead, the connection fails. See Credentials flows for the pattern and for building the form.

There are no OAuth scopes on Bluesky. What the account can do is whatever the app password allows.

#Post types

A post with only content is a text post. Add mediaItems for an image or video post.

curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "New release notes are up: https://example.com/changelog",
    "mediaItems": [{ "type": "image", "url": "https://pozy.ai/media/release.png", "altText": "The new settings screen" }],
    "platforms": [{ "platform": "bluesky", "accountId": "66b2e19d8c3f5a7e9d0b1c2d" }],
    "publishNow": true
  }'

Pozy turns plain text into Bluesky's rich-text links for you:

  • @handle.bsky.social becomes a mention.
  • #tag becomes a clickable hashtag.
  • A URL becomes a link, and Bluesky builds a preview card from the page's Open Graph tags. Put the URL at the end of the post and check the page has those tags.
Cross-posting

Bluesky's 300-character limit is strict and counts URLs and mentions. When the same post goes to platforms with longer limits, set customContent on the Bluesky entry with a shorter version. Otherwise only the Bluesky part fails.

#Threads

platformSpecificData.threadItems publishes a chain. The first item is the root post, each later item replies to the one before. Every item has its own text, its own media and its own 300-character limit. When threadItems is set, the top-level content is kept for display and search in Pozy but is not published.

Bluesky entry with a thread
{
  "platform": "bluesky",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": {
    "threadItems": [
      { "content": "Three things we changed in our API this quarter", "mediaItems": [{ "type": "image", "url": "https://pozy.ai/media/api.png" }] },
      { "content": "1. Cursor pagination on every list endpoint." },
      { "content": "2. Idempotency keys on every create." },
      { "content": "3. One error format everywhere." }
    ]
  }
}

Threads covers threads across platforms.

#Post language

langs tags a post with 1 to 3 BCP-47 codes, such as en or pt-BR. Bluesky's language feeds filter on it, so an untagged post never shows up in them. Set it per post, or set an account default that applies whenever a post has none:

curl -X PATCH https://api.pozy.ai/v1/accounts/66b2e19d8c3f5a7e9d0b1c2d/bluesky-settings \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "defaultLangs": ["pt"] }'

A per-post langs always wins. Send "defaultLangs": null to clear the default. Every item of a thread gets the same languages.

Field Type Notes
langs array of string 1 to 3 BCP-47 codes. Set at creation only.
threadItems array of { content, mediaItems } The whole thread, root first.

#Media requirements

Images Requirement
Per post Up to 4
Formats JPEG, PNG, WebP, GIF
Max size 1 MB per image. Larger images are compressed, which can cost quality
Max dimensions 2000 x 2000 px
Recommended 1200 x 675 px (16:9), 1000 x 1000 px (1:1) or 800 x 1000 px (4:5)
Alt text altText on each media item
Videos Requirement
Per post 1
Format MP4 only
Max size 50 MB
Max duration 60 seconds
Max dimensions 1920 x 1080 px
Recommended 1280 x 720 px, 30 fps, H.264 video, AAC audio

Videos outside these limits are rejected, not compressed. Compress large images yourself if quality matters. Hosting rules are on Media.

#Limits

Limit Value
Characters per post 300, including URLs and mentions
Characters per thread item 300 each
Images 4 per post
Videos 1 per post
Edits after publishing Not possible

Not available through Bluesky's API: lists and starter packs, custom feeds, pinning a post to the profile, content warnings and labels, DM attachments, and follower or profile analytics.

#Analytics

Get post analytics with platform=bluesky returns engagement counts only:

Metric Field
Likes likes
Replies comments
Reposts shares

Bluesky does not report impressions, reach, clicks or views, so those fields are empty for Bluesky posts.

#Inbox

Feature Supported
List and read conversations Yes
Send DMs Text only (Bluesky's chat API has no attachments)
Archive and unarchive Yes
List comments on your posts Yes
Reply to and delete comments Yes
Like and unlike a comment Yes

To like a comment, pass the comment's cid from the comments endpoints to Like comment. Keep the likeUri it returns, because Unlike comment needs it. See Messages and Comments.

#Errors you may see

Error Cause Fix
"Bluesky posts cannot exceed 300 characters" content is over the limit Shorten it, or set customContent on the Bluesky entry
"Thread item N exceeds 300 characters" One thread item is too long Split it into more items
Invalid app password on connect The main password was used, or the app password was revoked Create a new app password and connect again
Image too large An image is over 1 MB Compress it before upload to control quality
"Publishing failed due to max retries reached" Bluesky kept refusing the request Usually temporary. Retry the post

Check post.status and platforms[].errorMessage in the create response. A post can be accepted by Pozy and still fail on Bluesky. Errors has the error format.

#Endpoints

#Webhook events

Event When it fires
post.published Post published.
post.failed Post failed.
post.platform.published Post platform published.
post.platform.failed Post platform failed.
post.platform.deleted Post platform deleted.
message.received Message received.
comment.received Comment received.