Bluesky API

Use the Crosspostify API to publish image, multi-image and video posts to Bluesky — the open, decentralized social network built on the AT Protocol.

Post Types
Image · Multi-image (up to 4) · Video
Character Limit
300 characters (longer text is cut)
Protocol
AT Protocol (atproto)
Audience
Tech, open web, creators

Bluesky is a decentralized social network built on the AT Protocol — an open standard for social networking that gives users control over their identity and data. It has grown rapidly as an alternative to X/Twitter, with a strong community of developers, journalists, researchers, and creators.

The Crosspostify Bluesky integration supports three post types: image, multi-image (up to 4 images) and video. Text-only posts are not available for Bluesky (/post/text only accepts Facebook and Threads), and threads (reply chains) are not supported. Bluesky posts have a 300-character limit; Crosspostify cuts longer text to 300 characters when posting. The title parameter is not used on Bluesky — all content goes in caption.

This page covers everything specific to Bluesky when using the Crosspostify API. For general authentication, rate limits, and error handling, see the full API Reference.

Bluesky-Specific Parameters

Bluesky has a minimal parameter set. There are no platform-exclusive fields — just accountId and caption. The title parameter is not rendered on Bluesky and should be omitted.

accountId✓ AppliesRequired. Your Bluesky account ID from /social-accounts.
caption✓ AppliesThe post text. Cut to 300 characters when posting (no error). Hashtags, mentions and links stay plain text.
title✗ IgnoredNot rendered on Bluesky — omit this field. All content goes in caption.
link✗ IgnoredIgnored for Bluesky — put URLs in caption.
boardId✗ IgnoredPinterest only — ignored for Bluesky.
is_aigc✗ IgnoredTikTok, Instagram and Facebook only — ignored for Bluesky.
thumbnailTimestamp✗ IgnoredTikTok only — ignored for Bluesky.
facebookFirstComment✗ IgnoredFacebook only — ignored for Bluesky.
textFormatPresetId✗ IgnoredFacebook text posts only — ignored for Bluesky.
tiktok / instagram / facebook✗ IgnoredSettings for those platforms — ignored for Bluesky.
300-character limit

Bluesky has a 300-character limit per post. Captions longer than 300 characters are cut to the first 300 characters when posting. No error is returned, and GET /posts still shows the full caption. Emoji can count as 2 or more characters toward this cut. Plan your content accordingly — Bluesky rewards concise, punchy writing.

No rich-text facets

Bluesky uses a rich text system called facets to make links, hashtags and @mentions clickable. Crosspostify posts the caption as plain text: #hashtags, @handles and links are not converted to facets and are not clickable. Alt text is not supported either; images and videos are posted without alt text.

Supported Endpoints

Bluesky supports three Crosspostify post types. Text-only posts (/post/text) are not available for Bluesky.

POST/post/imageImage Post

Publish a single image to Bluesky. The image is embedded inline in the post alongside the caption text.

Image Post Notes

  • Caption and image appear together in a single post — captions over 300 characters are cut (no error)
  • Images are displayed inline in the feed
  • Crosspostify does not shrink images for Bluesky. Bluesky has its own image size limit, much smaller than the 20 MB Crosspostify accepts, so large images can fail at publish time. Keep image files small.
  • Images are posted without alt text (alt text is not supported)
  • Upload your image first via POST /media and pass the returned id as mediaId
  • scheduledDate accepts any ISO 8601 date-time. Z or an offset (e.g. +01:00) is converted to UTC; no offset means UTC. It must be in the future.
  • Omit scheduledDate to post immediately

Request Body

mediaIdstringrequired

The ID of the uploaded image file. Obtain this from POST /media.

scheduledDatestringoptional

ISO 8601 date-time in the future, e.g. 2030-01-15T10:00:00Z (offsets are converted to UTC). Omit to post immediately.

postsarrayrequired

Array of post objects. Include one object per Bluesky account.

posts[].accountIdstringrequired

The Bluesky account ID. Retrieve from GET /social-accounts.

posts[].captionstringoptional

Post text. Cut to 300 characters when posting (no error). Hashtags, mentions and links stay plain text.

Response

202 Accepted. status is SCHEDULED when scheduledDate is set, otherwise QUEUED. The post is published afterwards; check the result with GET /posts?id=<id>.

{ "mediaId": "img_12345", "message": "Image post created successfully", "posts": [ { "id": "7f3c9a2e-1b4d-4c8e-9f60-2a5b7d1e3c48", "platform": "bluesky", "socialAccountId": "acc_bluesky_321", "status": "SCHEDULED" } ] }

Code Example

curl -X POST "https://v1.api.crosspostify.com/post/image" \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "mediaId": "img_12345",
    "scheduledDate": "2030-01-15T10:00:00Z",
    "posts": [
      {
        "accountId": "acc_bluesky_321",
        "caption": "Golden hour over the Dolomites. Sometimes you just have to put the camera down and breathe it in. 🏔️ #landscape #photography #travel"
      }
    ]
  }'
POST/post/imagesMulti-Image

Publish 1 to 4 images in a single Bluesky post. Images are displayed in a grid alongside the caption text; a single image is posted as a normal image post.

Maximum 4 images per post

Bluesky supports a maximum of 4 images per post. This is Bluesky's limit, also checked by Crosspostify: more than 4 mediaIds returns 400 "Too many images" with the message "Bluesky supports a maximum of 4 images per post".

Multi-Image Notes

  • 1 to 4 images per post (Bluesky's limit, also checked by Crosspostify)
  • Images are displayed in the order provided in mediaIds
  • A single shared caption appears with all images; it is cut to 300 characters when posting
  • Bluesky may crop images in the multi-image grid preview of the feed
  • Images are posted without alt text and are not shrunk by Crosspostify; keep image files small
  • scheduledDate accepts any ISO 8601 date-time. Z or an offset (e.g. +01:00) is converted to UTC; no offset means UTC. It must be in the future.

Request Body

mediaIdsarrayrequired

Ordered array of 1 to 4 image media IDs. Images appear in this order. More than 4 returns 400.

scheduledDatestringoptional

ISO 8601 date-time in the future, e.g. 2030-01-15T10:00:00Z (offsets are converted to UTC). Omit to post immediately.

postsarrayrequired

Array of post objects. Include one object per Bluesky account.

posts[].accountIdstringrequired

The Bluesky account ID. Retrieve from GET /social-accounts.

posts[].captionstringoptional

Shared caption for all images. Cut to 300 characters when posting (no error). Hashtags, mentions and links stay plain text.

Response

202 Accepted. status is SCHEDULED when scheduledDate is set, otherwise QUEUED.

{ "mediaIds": ["img_001", "img_002", "img_003"], "message": "Multiple images post created successfully", "posts": [ { "id": "7f3c9a2e-1b4d-4c8e-9f60-2a5b7d1e3c48", "platform": "bluesky", "socialAccountId": "acc_bluesky_321", "status": "SCHEDULED" } ] }

Code Example

curl -X POST "https://v1.api.crosspostify.com/post/images" \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "mediaIds": ["img_001", "img_002", "img_003"],
    "scheduledDate": "2030-01-15T10:00:00Z",
    "posts": [
      {
        "accountId": "acc_bluesky_321",
        "caption": "Three frames from the shoot this morning. The light was doing something special. 📷 #photography #filmlook"
      }
    ]
  }'
POST/post/videoVideo Post

Publish a video to Bluesky. The video is attached to the post together with the caption text; Bluesky decides how it is processed and played in the feed.

Video Post Notes

  • Upload your video first via POST /media and pass the returned id as mediaId
  • Use MP4 files. MOV uploads are accepted but are sent to Bluesky as video/mp4 and may fail.
  • Caption appears alongside the video — captions over 300 characters are cut (no error)
  • Videos are posted without alt text
  • Bluesky's tech-forward audience responds well to demos, tutorials, and build-in-public content
  • scheduledDate accepts any ISO 8601 date-time. Z or an offset (e.g. +01:00) is converted to UTC; no offset means UTC. It must be in the future.

Request Body

mediaIdstringrequired

The ID of the uploaded video file. Obtain this from POST /media.

scheduledDatestringoptional

ISO 8601 date-time in the future, e.g. 2030-01-15T10:00:00Z (offsets are converted to UTC). Omit to post immediately.

postsarrayrequired

Array of post objects. Include one object per Bluesky account.

posts[].accountIdstringrequired

The Bluesky account ID. Retrieve from GET /social-accounts.

posts[].captionstringoptional

Post text alongside the video. Cut to 300 characters when posting (no error). Hashtags, mentions and links stay plain text.

Response

202 Accepted. status is SCHEDULED when scheduledDate is set, otherwise QUEUED. Bluesky may still reject the video after this; GET /posts then shows status FAILED with an error.

{ "mediaId": "vid_12345", "message": "Video post created successfully", "posts": [ { "id": "7f3c9a2e-1b4d-4c8e-9f60-2a5b7d1e3c48", "platform": "bluesky", "socialAccountId": "acc_bluesky_321", "status": "SCHEDULED" } ] }

Code Example

curl -X POST "https://v1.api.crosspostify.com/post/video" \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "mediaId": "vid_12345",
    "scheduledDate": "2030-01-15T10:00:00Z",
    "posts": [
      {
        "accountId": "acc_bluesky_321",
        "caption": "Built a small tool this weekend that auto-generates alt text for images using a local model. Here is a quick demo. 🛠️ #buildinpublic #opensource #ai"
      }
    ]
  }'

AT Protocol

Bluesky is built on the AT Protocol (atproto) — an open, federated protocol for social networking. Unlike centralized platforms, AT Protocol gives users portable identities, self-hosted data, and the ability to migrate between providers. Here's what that means for API users:

Decentralized Identity

  • Bluesky handles are domain-based (e.g. @you.bsky.social)
  • Custom domains work as handles (e.g. @yourname.com)
  • DIDs (Decentralized Identifiers) are the underlying identity layer
  • Crosspostify abstracts this — just use your accountId

Personal Data Servers (PDS)

  • All posts are stored in a user-controlled Personal Data Server
  • Posts are publicly accessible via the AT Protocol firehose
  • Content is portable — users can migrate their data
  • Crosspostify handles PDS interaction transparently

Rich Text Facets

  • Bluesky uses "facets" for inline rich text (links, hashtags, mentions)
  • Crosspostify does not create facets
  • Hashtags, @mentions and URLs are posted as plain text
  • Without facets they are not clickable in the Bluesky app

Open & Transparent

  • All posts are public by default — no private posts via API
  • Post records are cryptographically signed
  • The AT Protocol spec is open source and community-governed
You don't need to understand AT Protocol to use the API

Crosspostify handles the AT Protocol work — DID resolution, PDS authentication and lexicon record creation — behind the scenes. You just send a standard Crosspostify API request and we take care of the rest.

Content Guidelines

Bluesky's audience is tech-forward, values-driven, and highly engaged. Content that is authentic, conversational, and community-native performs best. The platform skews toward developers, journalists, researchers, and open-web advocates.

Image Requirements

  • Format: JPG, PNG or WEBP (converted to JPEG on upload)
  • Crosspostify accepts up to 20MB but does not shrink images for Bluesky
  • Bluesky has its own, much smaller image size limit — keep files small
  • Images appear inline in the feed
  • Max 4 images per post
  • No alt text (not supported by the API)

Video Requirements

  • Format: MP4 (MOV uploads are sent as video/mp4 and may fail)
  • Max file size: 100MB via Crosspostify
  • Bluesky limits video length and size; check the current Bluesky limits

Text & Caption Tips

  • 300-character limit; longer text is cut (no error)
  • Emoji can count as 2 or more characters toward the 300 cut
  • URLs are posted as plain text and count toward the limit
  • Short, conversational posts work best

Scheduling

  • Omit scheduledDate to post immediately
  • Any ISO 8601 time; offsets are converted to UTC, no offset = UTC
  • Must be in the future (past = 400); no minimum lead time
  • Published within about 1 minute of the scheduled time