API and developers

How to Post to the Bluesky API in 2026

Posting to Bluesky via the AT Protocol requires auth sessions, lexicon calls, and platform-specific formatting. The OmniSocials API wraps all of that into a single request.

Robert LigthartFounder of OmniSocialsPublished Updated 12 min read
How to Post to the Bluesky API in 2026

Bluesky is growing fast. It crossed 30 million users in early 2026 and the developer ecosystem around the AT Protocol is one of the most active in social media right now. If you want to post to Bluesky programmatically, you have two options: learn the AT Protocol directly (complex, low-level, verbose) or use the OmniSocials API (one request, done).

On this page
  1. What is the Bluesky API?
  2. How to Post to Bluesky Using the OmniSocials API
  3. Step 1: Get Your API Key
  4. Step 2: Make the API Call
  5. Step 3: Handle the Response
  6. Posting with Images
  7. Scheduling Bluesky Posts
  8. Posting to Bluesky and Other Platforms Simultaneously
  9. Native AT Protocol vs OmniSocials API: Side by Side
  10. Common Pitfalls
  11. Full API Reference
  12. Frequently Asked Questions
  13. Sources

This tutorial covers both. I'll show you the native approach briefly so you understand what's happening under the hood, then walk through how to post to Bluesky using the OmniSocials API with JavaScript and Python examples.

What is the Bluesky API?

The Bluesky API is built on the AT Protocol (Authenticated Transfer Protocol), an open decentralized social protocol. Unlike Twitter's API or LinkedIn's API, there is no traditional REST API with a single endpoint. Instead, you interact with a Personal Data Server (PDS) using lexicon-typed method calls.

In practice, posting a single text update to Bluesky via the native AT Protocol looks like this:

  1. Authenticate with com.atproto.server.createSession using an app password
  2. Receive a session token (JWT)
  3. Call com.atproto.repo.createRecord with a fully typed lexicon record (app.bsky.feed.post)
  4. Include a createdAt timestamp in ISO 8601 format
  5. Handle PDS-specific base URLs per user account

That is five steps before you have posted a single sentence. And that is without images.

With the OmniSocials API, it is one POST request.

How to Post to Bluesky Using the OmniSocials API

The OmniSocials API wraps the AT Protocol into the same unified endpoint you would use to post to Instagram, LinkedIn, or any other platform. One API key, one endpoint, one request.

Here is what you need to get started.

Step 1: Get Your API Key

Sign up at omnisocials.com and start your free 14-day trial.

From the dashboard:

  1. Connect your Bluesky account to your workspace
  2. Go to Settings > API, create an API key, and store it as OMNISOCIALS_API_KEY

That API key is all you need. No app passwords, no PDS URLs, no session tokens to manage.

Step 2: Make the API Call

Call client.posts.createAndPublish() with your text and bluesky in the channels array. The examples use the official SDKs for Node.js (npm install @omnisocials/sdk) and Python (pip install omnisocials), which read OMNISOCIALS_API_KEY from the environment.

JavaScript:

import { OmniSocials } from '@omnisocials/sdk';

const client = new OmniSocials(); // reads OMNISOCIALS_API_KEY

const { data: post } = await client.posts.createAndPublish({
  content: 'Just shipped something new. Check it out.',
  channels: ['bluesky'],
});

console.log(post.id, post.status); // "1019" "posting"

Python:

from omnisocials import OmniSocials

client = OmniSocials()  # reads OMNISOCIALS_API_KEY

post = client.posts.create_and_publish(
    content="Just shipped something new. Check it out.",
    channels=["bluesky"],
)

print(post["data"]["id"], post["data"]["status"])  # 1019 posting

Both examples publish immediately. Without the SDK, it's a POST to https://api.omnisocials.com/v1/posts with content, channels, and "publish_now": true in the JSON body.

Step 3: Handle the Response

The response wraps the post in data. The fields you care about:

  • id: your OmniSocials post ID, use this to retrieve or delete the post later
  • status: posting right after the call, then published, failed, or warning (some platforms failed). Scheduled posts show scheduled.
  • published_urls: the live link per platform, so you can confirm Bluesky specifically
  • errors: the reason per failed platform, or null

Fetch the latest state with client.posts.get(id). If errors.bluesky is set, that's your reason. Common causes are a disconnected account or a post that exceeds Bluesky's 300-character limit.

Posting with Images

Bluesky supports up to 10 images per post: up to 4 in the classic images embed, and 5 to 10 in the newer gallery embed (June 2026). Using the native AT Protocol, you would need to upload each image as a blob via com.atproto.repo.uploadBlob, get back a blob reference, and attach it to the post record manually.

With OmniSocials, pass the image URLs in media_urls:

await client.posts.createAndPublish({
  content: 'New feature screenshot:',
  channels: ['bluesky'],
  media_urls: ['https://yourcdn.com/screenshot.png'],
});

The OmniSocials API downloads the image, uploads it as a blob, and attaches it to the post record automatically.

Scheduling Bluesky Posts

Bluesky's native AT Protocol has no scheduling. If you want to publish at a specific time, you build that queuing logic yourself.

With the OmniSocials API, use create with a future scheduled_at (ISO 8601):

await client.posts.create({
  content: 'Our weekly update is live.',
  channels: ['bluesky'],
  scheduled_at: '2027-03-01T09:00:00Z',
});

The post queues and publishes automatically. List scheduled posts with client.posts.list({ status: 'scheduled' }) (GET /v1/posts?status=scheduled), change one with client.posts.update(), or cancel it with client.posts.delete().

Posting to Bluesky and Other Platforms Simultaneously

This is where the OmniSocials API becomes genuinely useful for multi-platform distribution. One request, multiple platforms:

await client.posts.create({
  content: 'Announcing our November update.',
  channels: ['bluesky', 'linkedin', 'mastodon', 'threads'],
  media_urls: ['https://yourcdn.com/announcement.png'],
  scheduled_at: '2027-03-01T10:00:00Z',
});

The AT Protocol, Instagram Graph API, LinkedIn API, and Mastodon API each have completely different auth flows, media upload requirements, and payload formats. The OmniSocials API handles all of that. You write one request.

[Screenshot: OmniSocials post editor showing a post queued for Bluesky, LinkedIn, Mastodon, and Threads]

Native AT Protocol vs OmniSocials API: Side by Side

Here is what posting a text update with one image looks like on each approach:

StepNative AT ProtocolOmniSocials API
Step: AuthNative AT Protocol: Create session with app password, store JWTOmniSocials API: Use your API key in the header
Step: Image uploadNative AT Protocol: com.atproto.repo.uploadBlob, store blob refOmniSocials API: Pass image URL in media_urls
Step: Create postNative AT Protocol: com.atproto.repo.createRecord with typed lexiconOmniSocials API: POST to /v1/posts
Step: SchedulingNative AT Protocol: Build your own queueOmniSocials API: scheduled_at field
Step: Multi-platformNative AT Protocol: Repeat entire flow per platformOmniSocials API: Add platforms to channels
Step: Total requestsNative AT Protocol: 4-6OmniSocials API: 1

The AT Protocol is well-documented and worth understanding if you are building something deeply integrated with the Bluesky ecosystem. For most use cases, posting content from an app or automation, the OmniSocials API is the practical choice.

Common Pitfalls

Character limits. Bluesky allows 300 characters per post. If your text exceeds this, the OmniSocials API returns a 400 validation error before attempting to publish, with the reason in error.message.

Account disconnection. Bluesky app passwords expire or get revoked if you change your account password. If posts start failing with a 401 from the platform layer, reconnect your Bluesky account in the OmniSocials dashboard.

Image format restrictions. Bluesky supports JPEG, PNG, and WebP. GIFs are not supported as animated images. OmniSocials converts HEIC to JPEG on upload, but it is better to send JPEG, PNG, or WebP from the start.

Rate limits. The OmniSocials API allows 100 requests per minute per API key. Bluesky itself has rate limits on the PDS level. The SDKs retry 429 and 5xx responses from the OmniSocials API for you, and if Bluesky rejects a post, POST /v1/posts/{id}/retry re-publishes only the platforms that failed.

Handle the failed status. Do not assume a 201 HTTP response means the post published. It means the request was accepted, not necessarily that Bluesky published it successfully. Check status and errors.bluesky on the post, or subscribe to the post.failed webhook.

Full API Reference

The full OmniSocials API documentation is at docs.omnisocials.com. It covers all endpoints, authentication, webhook setup, error codes, and platform-specific notes including Bluesky character limits and supported media types.

Frequently Asked Questions

Does Bluesky have a public API?

Yes. Bluesky is built on the AT Protocol, which is fully open and documented at docs.bsky.app. However, posting directly requires managing auth sessions, lexicon record types, and DID resolution yourself. The OmniSocials API abstracts all of that into a single authenticated request.

Do I need an app password to post to Bluesky via API?

When using the AT Protocol directly, yes. You generate an app password in Bluesky settings and use it to create a session token via com.atproto.server.createSession. With the OmniSocials API, you connect your Bluesky account once in the dashboard and then use your OmniSocials API key for everything.

Can I schedule posts to Bluesky via API?

Yes. Add a scheduled_at field to your OmniSocials API request with a future ISO 8601 timestamp. The post will queue and publish automatically at that time. Bluesky's native AT Protocol has no built-in scheduling, so you would need to build that logic yourself.

Can I post images to Bluesky via API?

Yes. With the OmniSocials API, include a media_urls list with your image URLs. The API handles the upload and blob attachment for you. Using the native AT Protocol, you would need to upload the image to the PDS via com.atproto.repo.uploadBlob first, then attach the blob reference manually to the post record.

How much does it cost to use the Bluesky API through OmniSocials?

OmniSocials costs $10/mo (annual) or $12/mo monthly. API access is included in every plan with no separate API tier or per-request pricing. You get up to 100 API requests per minute and access to all 13 supported platforms including Bluesky, Threads, and Mastodon.


Sources

Written byRobert LigthartFounder of OmniSocials

Try OmniSocials for 14 days

Plan, schedule and answer on 13 platforms from one calendar, or let your AI agent do it. The first charge comes when the trial ends.