Reelspan posts to Bluesky through the AT Protocol, signed in with an app password. You send the post once; Reelspan uploads the media, sends videos through Bluesky's video service so they play, and creates the post. Text longer than 300 characters becomes a thread.
What you can post
| Post | How | Notes |
|---|---|---|
| Video post | POST /v1/posts/video |
Up to 100 MB, or compressed for you |
| Image post | POST /v1/posts/photos |
Up to 4 images |
| Text post | POST /v1/posts/text |
|
| Thread | Any of the above with long text | Split at 300 characters |
Reelspan handles for you:
- Video processing. Videos go through Bluesky's video service, which transcodes them for playback; Reelspan waits until it is done (up to 10 minutes). The player gets the video's real aspect ratio.
- Image size. Bluesky takes images up to 1 MB. Larger images, and formats other than JPEG, PNG and WebP, are re-encoded as JPEG under 1 MB.
- Clickable links and hashtags. URLs and
#hashtagsin the text are turned into links and tags. - Threads. Text over 300 characters is split on paragraph breaks, then lines, then words. The first post carries the media; each next post replies to the one before.
Connect the account
Bluesky connects with your handle and an app password, never your main password.
- In Bluesky, open Settings → Privacy and security → App passwords and create one. It looks like
xxxx-xxxx-xxxx-xxxx. - In Reelspan, go to Accounts → Bluesky and enter your handle (or email) and the app password, or connect through the API with Connect with credentials:
curl -X POST https://api.reelspan.com/v1/accounts/bluesky \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"profile_username": "my-brand", "identifier": "mybrand.bsky.social", "app_password": "abcd-efgh-ijkl-mnop"}'await reelspan.request('POST', '/v1/accounts/bluesky', {
json: { profile_username: 'my-brand', identifier: 'mybrand.bsky.social', app_password: 'abcd-efgh-ijkl-mnop' },
});reelspan.accounts.connect("my-brand", "bluesky", identifier="mybrand.bsky.social", app_password="abcd-efgh-ijkl-mnop")Reelspan checks the app password with Bluesky before saving it, encrypted. To post videos, the Bluesky account's email address must be confirmed.
Example
A video post in English and German, compressed if it is over 100 MB:
curl -X POST https://api.reelspan.com/v1/posts/video \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-F "user=my-brand" \
-F "platform[]=bluesky" \
-F "bluesky_title=Our new studio, in one take. https://example.com/studio #design" \
-F "bluesky_langs=en,de" \
-F "bluesky_compress=true" \
-F "bluesky_alt_text=A walk through a bright studio with plants and desks" \
-F "[email protected]"import { Reelspan } from 'reelspan';
import { readFile } from 'node:fs/promises';
const reelspan = new Reelspan();
const post = await reelspan.posts.video({
user: 'my-brand',
platforms: ['bluesky'],
video: { data: await readFile('launch.mp4'), name: 'launch.mp4', type: 'video/mp4' },
extra: {
bluesky_title: 'Our new studio, in one take. https://example.com/studio #design',
bluesky_langs: 'en,de',
bluesky_compress: true,
bluesky_alt_text: 'A walk through a bright studio with plants and desks',
},
});from reelspan import Reelspan
reelspan = Reelspan()
post = reelspan.posts.video(
"my-brand", ["bluesky"], "launch.mp4",
extra={
"bluesky_title": "Our new studio, in one take. https://example.com/studio #design",
"bluesky_langs": "en,de",
"bluesky_compress": True,
"bluesky_alt_text": "A walk through a bright studio with plants and desks",
},
)Fields
| Field | Type | Values | What it does |
|---|---|---|---|
bluesky_title |
string | any length | The post text. Falls back to title. Over 300 characters it becomes a thread. |
bluesky_langs |
string | language codes separated by commas, up to 3 | The post's languages, e.g. en,de. |
bluesky_compress |
boolean | default false |
Videos over 100 MB: compress them so Bluesky takes them, instead of failing. |
bluesky_alt_text |
string or list | up to 1,000 characters each | Alt text: for the video, or for each image (one for every image, or one per image in order). Falls back to alt_text. |
bluesky_first_comment |
string | up to 300 characters | A reply posted under the new post. Falls back to first_comment. |
bluesky_reply_to_id |
string | an at:// post URI |
Posts this as a reply to that post, in its thread. |
bluesky_quote_uri |
string | an at:// post URI |
Quotes that post, with or without media of your own. |
bluesky_labels |
string | sexual, nudity, porn, graphic-media, separated by commas |
Content warnings on the post. Other values are ignored. |
bluesky_threadgate |
string | nobody, or any of mention, following, followers separated by commas |
Who can reply. Leave it out for everyone. |
bluesky_postgate |
string | no_quotes |
Nobody can quote the post. |
A reply or quote to a post Bluesky can't find fails with bluesky_post_not_found. If Bluesky doesn't take the reply or quote setting, the post still goes out, with a warning.
curl -X POST https://api.reelspan.com/v1/posts/text \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"user": "my-brand",
"platform": ["bluesky"],
"title": "This is the best write-up of the launch so far.",
"bluesky_quote_uri": "at://did:plc:abc123/app.bsky.feed.post/3kxyz",
"bluesky_threadgate": "following"
}'Limits
- Post: 300 characters (counted as people see them: an emoji is one). Longer text becomes a thread.
- Video: up to 100 MB. Bigger videos fail unless you send
bluesky_compress=true; then Reelspan re-encodes them (H.264, at most 1080p) to fit. - Images: up to 4 per post, 1 MB each after re-encoding. With more, the first 4 are used and the result says so.
- Shapes: 16:9 works best; other shapes keep their own ratio.
Good to know
- Confirmed email for video. Bluesky only takes videos from accounts with a confirmed email, and has a daily video limit per account. Reelspan checks both before uploading.
- No link cards. Links are clickable in the text, but no preview card is attached.
- A thread that stops halfway keeps the posts already made: the result is a success with a warning saying where it stopped.
- Result extras:
cid(the post's content id).post_idis the post'sat://URI. - First comment: posted as a reply to the new post.
- Analytics: followers, following and post count for the account; likes, reposts, replies and quotes per post.
After posting
- Delete a post with Unpublish. Only the connected account's own posts can be deleted.
- Repost any post from the account with Repost.
- Bluesky posts can't be edited.
For both, post_id can be the post's at:// URI or its bsky.app/profile/…/post/… link.
Errors
error_code |
What happened | What to do |
|---|---|---|
bluesky_email_unconfirmed |
The account's email isn't confirmed, so Bluesky refuses videos | In Bluesky open Settings → Account, confirm the email, then retry. |
bluesky_video_limit |
The account used up Bluesky's daily video limit | Retry tomorrow. |
bluesky_video_too_large |
The video is over 100 MB (and bluesky_compress wasn't set, or it is still too big after compressing) |
Retry with bluesky_compress=true, or send a smaller file. |
bluesky_upload_failed |
Bluesky refused the video or image upload | Read error; Reelspan retries server errors. |
bluesky_processing_failed |
Bluesky's video service couldn't process the video | Check the file and post again. |
bluesky_processing_timeout |
Bluesky took more than 10 minutes | Reelspan retries it. |
bluesky_error |
Bluesky refused the post | Read error for Bluesky's message. |
bluesky_post_not_found |
The post in bluesky_reply_to_id or bluesky_quote_uri doesn't exist |
Check the at:// URI. |
reauth_required |
The handle or app password no longer works (for example the app password was revoked) | Create a new app password and connect again. |
invalid_app_password |
When connecting: the password isn't in app-password format | Create an app password in Bluesky and use that. |