Post text, images, and video to Bluesky via the AT Protocol (atproto). Supports rich text with clickable links, hashtags, and @mentions resolved to their DIDs.
Quick Reference#
| Property | Value |
|---|
| Max text length | 300 graphemes |
| Max images | 4 per post |
| Max videos | 1 per post |
| Image formats | JPEG, PNG, WebP, GIF |
| Max image size | 1 MB per image (auto-resized) |
| Video formats | MP4, MPEG, WebM, MOV |
| Max video size | 100 MB |
| Max video duration | 3 minutes |
| Scheduling | Yes (via Kabonshare queue) |
| Delete via API | Yes |
Before You Start#
Bluesky uses atproto OAuth — not a developer app or client secret. You connect with your Bluesky handle (e.g. name.bsky.social) and authorise via the standard Bluesky consent screen. No app registration is required.
Video upload requires a verified email on the Bluesky account. If video publishing fails, go to the Bluesky app → Settings → Account and verify the email, then retry.
Bluesky counts text in graphemes, not characters or bytes. A single emoji counts as 1 grapheme. The 300-grapheme limit is enforced by the platform — Kabonshare measures and validates using the same RichText library Bluesky uses, so the count shown in the composer is always accurate.
Content Types#
Post
A standard text post. Supports up to 4 images or 1 video — not both in the same post. Rich text (links, hashtags, @mentions) is auto-detected and rendered as clickable facets.
Text only
Text-first platform — posts without media are fully supported and common. Links, hashtags, and @mentions are all automatically linkified.
Specs#
Authentication#
Bluesky uses atproto OAuth rather than a traditional client-credentials flow. Key differences from other platforms:| Property | Bluesky | Other platforms |
|---|
| Connection trigger | Requires the user's handle upfront | Just a platform name |
| Token storage | Managed by @atproto/oauth-client-node in its own session store | accessToken / refreshToken on SocialAccount |
| Token refresh | Transparent — the library refreshes automatically on every API call | Managed explicitly by Kabonshare |
| Token type | DPoP-bound access tokens (replay-resistant) | Bearer tokens |
The SocialAccount.accessToken field for a Bluesky account holds a placeholder sentinel value (atproto-oauth), not a real token. The live credentials are stored in BlueskyOAuthSession, keyed by the account's DID. This is correct by design — the OAuth library owns the token lifecycle.
Multi-Workspace Behaviour#
The same Bluesky account can be connected across multiple workspaces. Because atproto credentials are keyed by DID (not per-connection), all workspaces sharing the same Bluesky account share one token family. Rotating a token in one workspace does not invalidate others — the single BlueskyOAuthSession record is updated atomically for all of them.
Features#
| Feature | Supported |
|---|
| Scheduling | Yes (via Kabonshare queue) |
| Delete via API | Yes |
| Rich text (links, hashtags, mentions) | Yes — auto-detected |
| Images (up to 4, auto-resized) | Yes |
| Video (up to 100 MB / 3 min) | Yes — requires verified email |
| Analytics — followers / following / posts | Yes |
| Analytics — per-post (likes, reposts, replies, quotes) | Yes |
| Analytics — impressions / reach / demographics | No — not available via the AT Protocol API |
| Native scheduling | No — queue-based only |
| Container pre-processing | No |
Limitations#
Bluesky has no native scheduling API and no platform-provided impressions or demographic data. Scheduling is handled by Kabonshare's queue, and analytics are limited to public engagement counts (likes, reposts, replies, quotes).
A post cannot contain both images and video. If you include a video, any images in the same post are ignored. If you include multiple videos, only the first is used and the rest are rejected.
Troubleshooting#
| Symptom | Cause / Fix |
|---|
Video fails with 403 unconfirmed_email | The connected Bluesky account's email is not verified. Go to the Bluesky app → Settings → Account and verify it, then retry the post. |
Session could not be restored / must reconnect | The atproto OAuth session was revoked or expired. The user must disconnect and reconnect the account from the Accounts page. |
401 on video upload after service auth succeeded | The PDS DID resolution may have returned a stale value. The adapter resolves the PDS from the DID document on every video publish — check that plc.directory is reachable from the server. |
| Mention not linked | @mentions are resolved to the account's DID at publish time. If the handle doesn't exist on Bluesky, the mention is left as plain text rather than failing the post. |
| Post appears as plain text with no hashtag links | Rich text facets are detected server-side. If facet detection fails (network timeout reaching the PDS), the post is published as plain text — it still goes out, just without clickable facets. |