Skip to main content
Platforms

Bluesky

Publish posts and images to Bluesky via the PostPeer API.

Overview

Publish text and image posts to Bluesky via the AT Protocol. Unlike the other platforms, Bluesky uses an App Password flow rather than OAuth redirects — the user generates an app password in Bluesky's settings and submits it once to PostPeer.

Quick Start

1. Generate an App Password

The end user goes to https://bsky.app/settings/app-passwords, creates a new app password (recommended name: your application's name), and copies the four-group token (xxxx-xxxx-xxxx-xxxx).

Leave the "Allow access to your direct messages" option unchecked — PostPeer never reads or sends DMs.

2. Connect the Account

curl -X POST https://api.postpeer.dev/v1/connect/bluesky/auth \
  -H "x-access-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "alice.bsky.social",
    "password":   "xxxx-xxxx-xxxx-xxxx"
  }'

identifier accepts a handle (alice.bsky.social) or a DID (did:plc:...). password must be an app password — Bluesky's anti-abuse system blocks regular account passwords used over the API.

Response:

{
  "success": true,
  "message": "Bluesky connected.",
  "integration": {
    "id": "65ab...",
    "platform": "bluesky",
    "platformUserId": "did:plc:...",
    "displayName": "@alice.bsky.social",
    "imageUrl": "https://assets.postpeer.dev/...",
    "createdAt": "2026-04-24T..."
  }
}

Save integration.id — that's the accountId you'll pass when posting.

3. Publish a Post

curl -X POST "https://api.postpeer.dev/v1/posts" \
  -H "x-access-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello from PostPeer 👋 https://postpeer.dev",
    "platforms": [
      { "platform": "bluesky", "accountId": "<integration id>" }
    ],
    "publishNow": true
  }'

Response:

{
  "success": true,
  "status": "published",
  "platforms": [
    {
      "platform": "bluesky",
      "success": true,
      "platformPostId": "at://did:plc:.../app.bsky.feed.post/3kabc...",
      "platformPostUrl": "https://bsky.app/profile/did:plc:.../post/3kabc..."
    }
  ]
}

URLs in the post text are auto-detected and converted to Bluesky facets so they render as clickable links.

Features

Images

Attach up to 4 images per post via the standard mediaItems array. Each image must be 1 MB or less (a Bluesky API hard limit).

curl -X POST "https://api.postpeer.dev/v1/posts" \
  -H "x-access-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Sunset shots",
    "platforms": [
      {
        "platform": "bluesky",
        "accountId": "<integration id>",
        "platformSpecificData": {
          "altText": ["Pink sunset over the bay", "Same scene 30 minutes later"]
        }
      }
    ],
    "mediaItems": [
      { "type": "image", "url": "https://cdn.example.com/sunset-1.jpg" },
      { "type": "image", "url": "https://cdn.example.com/sunset-2.jpg" }
    ],
    "publishNow": true
  }'

Languages

Set langs on platformSpecificData to declare the post's language(s) using BCP-47 tags. Defaults to ["en"] when omitted.

{
  "platform": "bluesky",
  "accountId": "<integration id>",
  "platformSpecificData": {
    "langs": ["en", "es"]
  }
}

Limits

LimitValue
Character limit300 graphemes per post
Images per post4
Image size1 MB max each
Video / GIFNot currently supported

Token Refresh

Bluesky access tokens expire after about 2 hours. PostPeer refreshes them automatically using the refresh token returned by createSession, and persists the rotated pair on every refresh — your integration stays connected for as long as the user keeps the app password active.

If the user revokes the app password in Bluesky's settings, PostPeer will return an authentication error on the next post; you'll need to reconnect through the same /v1/connect/bluesky/auth endpoint.

On this page