· 2 min read
How to post to Bluesky with an API (and schedule it) in 10 lines
Publish and schedule Bluesky posts from your app with a single REST call. No developer app to register. Includes cURL, JavaScript and Python.
Bluesky is one of the easiest social networks to automate. There's no developer app to register and no review queue to wait in. But "easy" still means handling sessions, record formats, facets for links and mentions, and image blobs, and Bluesky has no native scheduling at all.
This guide shows how to publish and schedule Bluesky posts with one REST call using Hapi. The same request can also post to X and LinkedIn at the same time.
Step 1: Create an app password on Bluesky
In Bluesky, go to Settings → Privacy and security → App passwords → Add app password. Give it a name like hapi and copy it.
App passwords can't change your account password or delete your account, so they're the right credential to hand to an integration.
Step 2: Connect the account in Hapi
Create a free Hapi account, open Accounts → Connect → Bluesky, and paste your handle and app password. Hapi encrypts the credential at rest (AES-256-GCM), and it's never returned to the browser.
Then create an API key under API keys. It's shown once, so put it in your environment:
export HAPI_API_KEY="hapi_live_..."Step 3: Publish a post
cURL
curl -X POST https://hapi.tools/v1/posts \
-H "Authorization: Bearer $HAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello Bluesky, posted from an API 🦋",
"platforms": ["bluesky"]
}'JavaScript
const res = await fetch("https://hapi.tools/v1/posts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HAPI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
content: "Hello Bluesky, posted from an API 🦋",
platforms: ["bluesky"],
}),
});
console.log(await res.json());Python
import os, requests
res = requests.post(
"https://hapi.tools/v1/posts",
headers={"Authorization": f"Bearer {os.environ['HAPI_API_KEY']}"},
json={"content": "Hello Bluesky, posted from an API 🦋", "platforms": ["bluesky"]},
)
print(res.json())That's it. You get a 201 with the post and its per-account status.
Step 4: Schedule it for later
Bluesky doesn't support scheduled posts natively, so normally you'd build your own job runner. With Hapi, you add scheduledAt:
{
"content": "This goes out Monday morning",
"platforms": ["bluesky"],
"scheduledAt": "2026-11-16T09:00:00-03:00"
}Hapi's scheduler publishes it on time and fires a signed post.published (or post.failed) webhook, so your app knows the outcome.
Step 5: Post to Bluesky, X and LinkedIn at once
Same request, more platforms:
{
"content": "We just shipped v2 🚀",
"platforms": ["bluesky", "x", "linkedin"],
"platformOptions": {
"bluesky": { "content": "We just shipped v2 🚀 (Bluesky gets the short version)" }
}
}platformOptions lets you tweak copy per network. That's handy because Bluesky caps posts at 300 characters.
Don't double-post on retries
Network blips happen. Send an Idempotency-Key header and Hapi returns the original post instead of creating a duplicate:
-H "Idempotency-Key: release-v2-announcement"This matters even more if an AI agent is the one calling the API.
What you skipped by not building it yourself
- Session creation and refresh against the AT Protocol
- Building link and mention facets by hand
- Uploading image blobs and attaching them to records
- A scheduler, retries and failure notifications
Start your 15-day trial → The $5 trial includes 5 connected accounts and 300 posts over 15 days.