yeetpost

Bluesky

#Overview

Post to Bluesky, and to any other server that speaks the AT Protocol. There is no OAuth dance: you create an app password in your own Bluesky settings and yeetpost trades it for a session. Delete the app password and every session made from it dies with it.

Bluesky posts are free to send. The account counts as one connected account, like LinkedIn or X. See pricing.

#How to connect

  1. In Bluesky, open Settings, then App Passwords, and create one. Never use your account password.
  2. In yeetpost, click Add connection, then Bluesky.
  3. Paste your handle (you.bsky.social) and the app password (xxxx-xxxx-xxxx-xxxx).
  4. Running your own PDS? Open On your own server? Set the service URL and give the host. It has to be https. The default is https://bsky.social.

Your first Bluesky connection gets the slug bluesky. A second account gets a slug built from its handle, so alex.bsky.social becomes alexbskysocial_bluesky. GET /connections lists yours, described as @alex.bsky.social (Bluesky).

The account is identified by its DID, not by the handle, so renaming your handle does not break the connection. Reconnecting the same account replaces the stored app password in place instead of adding a second connection.

#What works

ThingValue
Text limit300 graphemes, so a multi-codepoint emoji counts once
ImagesUp to 4 per post, 1 MB each
Image formatsimage/png, image/jpeg, image/gif, image/webp
Alt textYes, sent with each image
ThreadsYes, up to 24 replies under the head post
First commentNo, LinkedIn only
SchedulingYes
Permalinkhttps://bsky.app/profile/<handle>/post/<record key>
Per-post feeNone

The 300 is checked before anything is published, and every segment of a thread is measured the same way, so a thread with one segment over the limit is refused whole rather than left half published.

A broken thread is not taken back. Bluesky has no rollback of its own, so if the platform refuses the third reply, the head and the first two stay up. The item comes back sent with threadCount at the replies that actually went out. Read the count rather than assuming the whole thread landed.

Images ride on the head post. A thread with an image per segment is not something yeetpost does yet.

#Refusals

Every one of these is a per-item refusal: the other connections on the same post still go out. See the errors reference for the full vocabulary.

CodeStatusWhen
invalid_request400Text over 300 graphemes, in the post or in any thread segment
media_unsupported400More than 4 images, or an image over 1 MB
shape_unsupported400A firstComment was given. Bluesky has no first comment
unauthorized401The app password was revoked. Create a new one and reconnect
platform_rejected422The server said no, for example a duplicate post
internal_server_error500Our fault. Quote the req_id when you report it

#Post from curl

curl -X POST https://api.yeetpost.com/api/v2/posts \ -H "x-api-key: $YEETPOST_API_KEY" \ -H "content-type: application/json" \ -d '{ "text": "we rewrote the scheduler last month. three things we got wrong:", "connectionSlugs": ["bluesky"], "thread": [ "1. polling every second. the queue is a table, and postgres is fine with SKIP LOCKED.", "2. retrying forever. five attempts and a dead letter beats an infinite loop." ] }'

#Post from Node

const response = await fetch("https://api.yeetpost.com/api/v2/posts", { method: "POST", headers: { "x-api-key": process.env.YEETPOST_API_KEY, "content-type": "application/json", "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ text: "shipped the new api docs today", connectionSlugs: ["bluesky"], }), }); const data = await response.json(); console.log(data.results[0].status, data.results[0].url);

#FAQ

#Do I need my Bluesky account password?

No, and you should not use it. An app password only posts, it is created under Settings, App Passwords, and you can revoke it at any time without touching your account.

#Does a self-hosted PDS work?

Yes. Give the service URL when you connect. It has to be https and it has to be a public host. Everything else, including the 300 grapheme limit and image handling, works the same way.

#Why is my emoji-heavy post accepted when it looks longer than 300?

The limit is graphemes, not code points. A flag or a skin-tone emoji is several code points and one grapheme, so it counts once.

#My thread came back sent but only two replies are there. Why?

Bluesky cannot delete a half-published thread on our behalf, so a refused reply stops the chain and leaves what already went out. threadCount says how many replies survived.

#Does an image over 1 MB fail the whole post?

No. Only the Bluesky item comes back failed with media_unsupported. Every other connection on the same request still goes out.

Other platforms