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
| Limit | Value |
|---|---|
| Character limit | 300 graphemes per post |
| Images per post | 4 |
| Image size | 1 MB max each |
| Video / GIF | Not 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.