How to Post to Bluesky via API
The request flow for publishing to Bluesky programmatically.
To post to Bluesky via API, connect the account with its handle and an app password (generated at bsky.app under Settings → App passwords) through PostGate’s hosted connection page, then send POST /api/posts with post.body, the profile ID, and optional media. Text is limited to 300 graphemes, with up to 4 images and no video yet.
Bluesky is the one platform in PostGate's catalog where the underlying protocol — the AT Protocol — is genuinely different from the REST APIs behind Instagram, LinkedIn, or TikTok. That shows up in exactly one place for you as an integrator: how the account gets connected. Everything after that — creating the post, tracking delivery, reading the result — uses the same request shape as every other platform.
This walkthrough covers the full flow end to end: register an origin, connect an account with an app password, publish, and confirm delivery.
What do you need before you start?
A PostGate API key, a registered connect origin for the hosted redirect, and a Bluesky account with an app password generated at bsky.app under Settings → App passwords.
The account's main login password should never be entered anywhere outside bsky.app itself — that's exactly what app passwords exist to avoid.
Step 1: register your product’s origin
curl -X POST https://postgate.studio/api/connect-origins \
-H "Authorization: Bearer $POSTGATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "origin": "https://app.example.com", "label": "Production SaaS" }'Step 2: create a hosted connection for Bluesky
Request a connection URL with platform: "bluesky" and redirect the customer's browser to it. The URL expires after 15 minutes.
curl -X POST https://postgate.studio/api/profiles/connect \
-H "Authorization: Bearer $POSTGATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platform": "bluesky",
"profile_group_id": "grp_acme123",
"return_url": "https://app.example.com/settings/social"
}'Step 3: the customer enters handle and app password
return_url with connected=1&connection_platform=bluesky, or connection_error if the customer canceled or the credential was rejected.Step 4: publish a post
post.body, a profiles array, and optional media. A stable Idempotency-Key makes the request safe to retry.curl -X POST "https://postgate.studio/api/posts?profile_group_id=grp_acme123" \
-H "Authorization: Bearer $POSTGATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bsky-launch-001" \
-d '{
"post": { "body": "Launch day. 🚀" },
"profiles": ["pro_bluesky123"],
"media": ["https://cdn.example.com/launch.jpg"]
}'Step 5: confirm delivery
published carries a permalink you can show the customer or store for your own records.curl https://postgate.studio/api/posts/post_01JXYZ \
-H "Authorization: Bearer $POSTGATE_API_KEY"What gotchas are specific to Bluesky?
GET /api/profiles/{id}/health) is worth calling before a big send — an app password can be revoked from Bluesky's side at any time, which shows up as reconnect_required rather than a silent failure.Frequently asked questions
Do I need an app password to post to Bluesky via API?
Yes. On PostGate’s hosted page the customer enters their Bluesky handle and an app password generated at bsky.app under Settings → App passwords — the one credential-based exception among PostGate’s mostly-OAuth connection flows. The account’s main login password should never be entered anywhere outside bsky.app itself; that’s exactly what app passwords exist to avoid.
Is posting to Bluesky different because of the AT Protocol?
Only at connection time. The AT Protocol is genuinely different from the REST APIs behind Instagram, LinkedIn, or TikTok, but that shows up in exactly one place for an integrator: how the account gets connected. Everything after that — creating the post, tracking delivery, reading the result — uses the same request shape as every other platform.
What are Bluesky’s limits for posts created via API?
A 300-grapheme text limit, up to 4 images per post, and no video yet. The publish envelope itself is standard: post.body, a profiles array, optional media, and a stable Idempotency-Key to make the request safe to retry.
What happens if the Bluesky app password is revoked?
An app password can be revoked from Bluesky’s side at any time. That shows up as reconnect_required rather than a silent failure — which is why calling the profile health check (GET /api/profiles/{id}/health) is worth doing before a big send.
