Docs
Docs/Platforms/Bluesky

Bluesky

Publishing to Bluesky via the Octopost API

Bluesky

Bluesky supports text posts, images, and threads. Octopost handles session management, grapheme counting, facet generation for clickable links and mentions, image blob uploads, and reply chain threading automatically -- you just send us your content.

Content Limits

TypeLimit
Text length300 graphemes (not characters -- emojis and non-Latin scripts count differently)
Images per post4
Image file size1 MB per image
Image formatsJPEG, PNG
VideoNot supported
PollsNot supported

Good to Know

  • Graphemes, not characters. Bluesky measures text length in graphemes. A single emoji counts as 1 grapheme even if it is multiple bytes or code points. "Hello".length in JavaScript gives you code units, not graphemes. Octopost counts graphemes for you and rejects posts that exceed 300 before they hit Bluesky's servers.
  • 1 MB image limit is small. This is tighter than most platforms. If your images are large, resize them before sending. Octopost will report a clear error if an image exceeds the limit.
  • Alt text matters. Bluesky supports alt text on images and the community values it. Include it when you can.
  • Links and @mentions must have facets to be clickable. Plain URLs and @handles in Bluesky post text are rendered as plain text unless accompanied by byte-indexed facet metadata. Octopost generates these facets automatically from your plain text -- just write naturally and include URLs and @handles as you normally would.

Threads

Send a thread by including an array of content items. Octopost publishes them in order, wiring up the reply references so they appear as a connected thread on Bluesky.

{
  "platforms": ["bluesky"],
  "thread": [
    { "content": "First post in the thread." },
    { "content": "Second post, continuing the thought." },
    { "content": "Final post. https://octopost.ink" }
  ]
}

Octopost manages the reply chain -- each post is linked to the previous one using the correct root and parent references.

Rate Limits

Bluesky does not publish official rate limits. Octopost uses conservative pacing (1 request per second) and tracks responses to back off automatically if the server signals throttling.

Error Reporting

What happenedWhat Octopost reports
Text exceeds 300 graphemesRejected before sending, with the grapheme count
Image exceeds 1 MBRejected before sending, with the file size
Invalid or revoked App Passwordauth_failed -- user needs to generate a new App Password in Bluesky settings
Session expired mid-publishOctopost refreshes the session automatically and retries. You will not see this error unless the refresh JWT is also expired, in which case: auth_failed
Facet position errorOctopost generates facets for you, so this should not occur. If it does, the post may be created but links or mentions will appear as plain text

Things Octopost Handles for You

You do not need to worry about any of the following, but in case you are curious:

  • Authentication via App Passwords. Bluesky does not use OAuth. Users provide their handle and an App Password (generated in Bluesky settings). Octopost creates and manages the AT Protocol session, storing the DID, access JWT, and refresh JWT securely.
  • Session refresh. Access JWTs expire frequently. Octopost detects expiration and refreshes the session using the refresh JWT before publishing, transparently.
  • Grapheme counting. Octopost uses a grapheme-aware counter to validate text length accurately before sending.
  • Facet generation. Octopost scans your text for URLs and @mentions, resolves mentioned users' DIDs, calculates UTF-8 byte offsets, and builds the facet array that Bluesky requires for clickable links, mentions, and hashtags.
  • Image blob upload. Bluesky requires images to be uploaded as blobs via the AT Protocol before they can be attached to a post. Octopost uploads each image, receives the blob reference, and constructs the embed object.
  • Reply chain management for threads. Each post in a thread must reference both the root post and its immediate parent via their AT URIs and CIDs. Octopost tracks these across the thread and sets them correctly.

Example

{
  "content": "Hello from Octopost! Check out https://octopost.ink @jake.bsky.social",
  "platforms": ["bluesky"],
  "media": {
    "images": [
      { "url": "https://example.com/photo.jpg", "alt": "A screenshot of the dashboard" }
    ]
  }
}

That's it. Octopost takes care of the rest.