Bluesky API: Post
How to publish a post to Bluesky via API using an app password.
Connect the Bluesky account with POST /api/profiles/connect (platform: bluesky) — the account holder enters their handle and an app password on PostGate's hosted page, never their main password — then publish with a single POST /api/posts naming the connected profile. Posts are capped at 300 graphemes, up to 4 images at 1 MB each, and no video yet.
Posting to Bluesky through the PostGate API looks like every other platform from the outside — one POST /api/posts request, one connected profile ID. What's different is what happens underneath: Bluesky runs on the AT Protocol, and the account you connect authenticates with an app password, not an OAuth redirect like Instagram, LinkedIn, or TikTok.
This page covers the single-request mechanics. For the full connect-to-confirm walkthrough, see How to Post to Bluesky via API.
Why is this one account a password, not an OAuth token?
Every other network in PostGate's catalog authorizes through an OAuth redirect: the customer leaves your product, approves a scoped grant on the provider's own page, and comes back with a token PostGate stores. Bluesky doesn't have that flow for third-party publishing tools in the same shape. Instead, the account holder generates an app password from Bluesky's own settings — a credential scoped to API access that can be revoked independently of the account's main login.
PostGate's hosted connection page collects that app password directly and exchanges it for a session behind the scenes; your product never touches or stores it. The account's main password should never be entered anywhere except bsky.app itself.
Connect the Bluesky account
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"
}'Publish a post
post.body, a target list of profiles, and optional media. Bluesky posts are capped at 300 graphemes, so keep the copy short if you're also fanning it out to platforms with a longer limit.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"]
}'What comes back?
targets entry for the Bluesky profile with delivery status, the underlying record identifiers, and — once published — a permalink you can store or show to the customer. Poll GET /api/posts/{id} or subscribe to a webhook instead of blocking on the initial call.Which limits actually bite?
Frequently asked questions
Does Bluesky use OAuth for third-party posting tools?
Not in the same shape as Instagram, LinkedIn, or TikTok. The account holder generates an app password from Bluesky’s own settings — a credential scoped to API access that can be revoked independently of the main login. PostGate’s hosted connection page collects that app password and exchanges it for a session behind the scenes; your product never touches it, and the main password should never be entered anywhere except bsky.app itself.
What is the character limit for a Bluesky post?
Bluesky posts are capped at 300 graphemes. If you are fanning the same body out to platforms with longer limits, keep the copy short enough to fit Bluesky’s cap, or the Bluesky target will fail while the others publish.
Can I post video to Bluesky through the API?
Not yet — Bluesky’s own video support hasn’t reached this API. The media limits that apply today: up to 4 images per post, with a 1 MB per-image cap. Links and hashtags in the body become clickable automatically, and @mentions resolve when the handle is real.
How do I confirm a Bluesky post actually published?
The response includes a targets entry for the Bluesky profile with delivery status, the underlying record identifiers, and — once published — a permalink you can store or show to the customer. Poll GET /api/posts/{id} or subscribe to a webhook instead of blocking on the initial call.
