Complete Guide to Bluesky API Integration: Authorization, Posting, Analytics & Comments
In the ever-changing landscape of social media platforms, Bluesky is emerging as a promising decentralized alternative to traditional networks – they recently hit 28 million registered Bluesky users and continue to grow. Built on the AT Protocol (formerly known as the Authenticated Transfer Protocol), Bluesky offers a unique approach to social networking that emphasizes user control, data portability, and algorithmic choice, plus an easy to use Bluesky API.
An interesting bit of history is that Bluesky was founded by Jack Dorsey, who was one of the Twitter founders. He was no longer involved with Bluesky as of 2019.
For developers and businesses looking to integrate social media functionality into their applications, understanding how to work with the Bluesky API is becoming increasingly important. This comprehensive guide will walk you through everything you need to know about integrating with Bluesky, from basic authentication and publishing posts (text, image, and videos) to advanced features like analytics and comment management.
As you go through this guide, we recommend referencing the Bluesky API docs. If you don’t want to handle the integration yourself, you can check out Ayrshare’s social API.
Why Choose Bluesky for Your Social Media Integration?
Bluesky stands out in several key ways:
- Decentralized Architecture: Built on the AT Protocol, Bluesky allows users to choose their hosting provider while maintaining interoperability across the network. This design safeguards user autonomy by allowing individuals to retain full access to their data, even in the event of unfavorable changes to Bluesky’s platform – theoretically eliminating platform lock-in.
- Algorithm Transparency: Users can select and customize their content algorithms, providing more control over their feed experience. And since Bluesky is open-source, you can see exactly what is going on behind the scenes.
- Data Portability: The platform’s architecture makes it easier for users to move their data between different service providers. This is one of the advantages of a decentralized architecture.
- Growing User Base: Despite being relatively new, Bluesky has attracted a significant and engaged user community, particularly among tech-savvy early adopters with 28M monthly active Bluesky users. Also, Bluesky has been known as a relatively friendly place to hang-out.
Getting Started with the Bluesky API
Let’s dive into the technical implementation using Node.js and the official @atproto/api
package.
Installation and Setup
Before starting the API integration, you’ll need to first set up your development environment properly. Start by installing the required dependencies. The @atproto/api
package provides the core functionality for interacting with Bluesky with Node.js, while dotenv
helps manage sensitive configuration data securely. As an alternative, you can always make Rest JSON calls to the AT Protocol.
First, install the required dependencies:
npm install @atproto/api dotenv
Configuration Setup
The configuration file is crucial for managing your Bluesky credentials securely. We’ll use environment variables to keep sensitive data out of your codebase. This approach follows security best practices and makes it easier to manage different configurations for development and production environments.
We’ll review how to get your Bluesky identifier and app password later on in the User Authorization section.
Create a basic configuration file:
// config.js
require('dotenv').config();
const config = {
BLUESKY_IDENTIFIER: process.env.BLUESKY_IDENTIFIER,
BLUESKY_PASSWORD: process.env.BLUESKY_PASSWORD
};
module.exports = config;
You’ll also need to create a .env
file in your project root with your Bluesky credentials:
BLUESKY_IDENTIFIER=your.username.bsky.social
BLUESKY_PASSWORD=your_password
Make sure to add .env
to your .gitignore
file to prevent accidentally committing sensitive credentials.
User Authorization
Authentication is the first step in interacting with the Bluesky API. The platform uses a combination of DID (Decentralized Identifier) and traditional username/password authentication. The authentication process returns JWT tokens that you’ll use for subsequent API requests.
As opposed to other social networks that use 3-legged OAuth for authorization, such as LinkedIn and Facebook, Bluesky uses the user’s Bluesky handle, such as myname.bsky.social
and an “app password”. The Bluesky app password is used to sign in to other Bluesky clients without giving full access to your account or password. Users will create this one-time app password in settings section of Bluesky.
Here’s how to implement user authentication with the handle and app password:
const { BskyAgent } = require('@atproto/api');
const config = require('./config');
const authenticateBlueskyUser = async () => {
const agent = new BskyAgent({
service: 'https://bsky.social'
});
try {
await agent.login({
identifier: config.BLUESKY_IDENTIFIER, // Bluesky handle
password: config.BLUESKY_PASSWORD // Bluesky App password
});
console.log('Successfully authenticated!');
return agent;
} catch (error) {
console.error('Authentication failed:', error);
throw error;
}
};
When authentication is successful, the API returns a JSON response with user information and authentication tokens:
{
"success": true,
"data": {
"did": "did:plc:abcdef123456",
"handle": "username.bsky.social",
"email": "[email protected]",
"accessJwt": "eyJhbG...",
"refreshJwt": "eyJhbG..."
}
}
This JSON response contains several important pieces of information:
did
: The user’s decentralized identifier, which is their unique ID in the AT Protocol.handle
: The user’s Bluesky handle, which is their human-readable username.email
: The associated email address.accessJwt
: A short-lived JWT token for making authenticated requests.refreshJwt
: A long-lived JWT token for obtaining new access tokens.
Store these tokens securely, as you’ll need them for all authenticated requests. The access token typically expires after a few hours, at which point you’ll need to use the refresh token to obtain a new one.
Important to Note: In Node, always use function scope instances of the Bluesky agent
object and never a globally scoped object. When you create the agent
object it is associated with the authenticated user.
Bluesky Posts
Publishing a Bluesky Text Post
Creating text posts on Bluesky is straightforward. Each post requires a text content and creation timestamp. The API allows up to 300 characters per post, and you can include markdown-style formatting and hashtags. When creating a post, the API generates a unique identifier that you can use for future interactions like getting analytics or managing replies.
Here’s how to create a basic text post:
const post = "This is #amazing";
const createTextPost = async (agent, post) => {
try {
const response = await agent.post({
$type: "app.bsky.feed.post", // The AT Protocal type
text: post,
createdAt: new Date().toISOString() // Required format
});
console.log('Post created:', response);
return response;
} catch (error) {
console.error('Failed to create post:', error);
throw error;
}
};
When successful, the API returns a response containing the post’s unique identifier and timestamp. Here’s an example response:
{
uri: "at://did:plc:abcdef123456/app.bsky.feed.post/123456789",
cid: "bafyreib2xGD4x…",
success: true
}
The response includes:
– uri
: A unique identifier for the post that can be used for future interactions.
– cid
: The content identifier in the AT Protocol.
– success
: Confirmation that the post was created successfully.
There are more advanced features such as resolving URL links and Bluesky mentions. To handle those you’ll want to use the RichText function:
const post = "This is @ayrshare.com and https://www.google.com";
const rt = new RichText({ text: post });
rt.detectFacets(agent);
agent.post({
$type: "app.bsky.feed.post",
text: rt.text,
facets: rt.facets,
createdAt: new Date().toISOString()
})
Publishing a Bluesky Image Post
Image posts on Bluesky require a two-step process:
- First uploading the image to Bluesky’s blob storage.
- Second creating a post that references the uploaded image.
The API supports various image formats including JPEG, PNG, and GIF. Each image upload returns a blob reference that you can use in your post.
Image posts can include both text content and multiple images (up to 4 per post). See here for Bluesky image and video media requirements.
The following code demonstrates how to create a post with a single JPEG image using an embed.
const createBlueskyImagePost = async (agent, post, imageBuffer) => {
try {
// Upload video to Bluesky's blob storage
const uploadResponse = await agent.uploadBlob(imageBuffer, {
encoding: 'image/jpeg'
});
const imageData = await getImageData(imageBuffer); // Function to get the dimensions
const response = await agent.post({
text: post,
embed: {
$type: 'app.bsky.embed.images',
images: [{
alt: 'Image description',
image: uploadResponse.blob,
aspectRatio: { // Optional, but recommended
width: imageData.width,
height: imageData.height
}
}]
},
createdAt: new Date().toISOString()
});
return response;
} catch (error) {
console.error('Failed to create image post:', error);
throw error;
}
};
While not required, the original image aspect ratio is a good idea to provide – it helps Blusky show the image as intended. You can create a function that uses ffprobe to provide the height and width.
A successful image post creation returns a response similar to text posts, but with additional information about the uploaded media:
{
uri: "at://did:plc:abcdef123456/app.bsky.feed.post/123456789",
cid: "bafyreib2xGD4x...",
success: true,
blob: {
ref: {
$link: "bafyreib2xGD4x..."
},
mimeType: "image/jpeg",
size: 245678
}
}
This response includes:
- Standard post information (
uri
andcid
). - The blob reference for the uploaded image.
- The image’s MIME type and size.
- Confirmation of successful upload and post creation.
The blob reference is particularly important as it can be used to reference the image in future posts or to track media usage in your application.
Publishing a Bluesky Video Post
Video sharing on Bluesky is similar to image posting but with specific requirements for video formats and size limits. The platform supports common video formats like MP4 and MOV, with a maximum file size of 1GB. Videos can be uploaded alongside text content and require proper encoding for optimal playback.
const createBlueskyVideoPost = async (agent, post, videoBuffer) => {
try {
// Upload video to Bluesky's blob storage
const uploadResponse = await agent.uploadBlob(videoBuffer, {
encoding: 'video/mp4'
});
// Create post with the uploaded video
const response = await agent.post({
text: post,
embed: {
$type: 'app.bsky.embed.video',
video: uploadResponse.blob,
aspectRatio: { // Optional, but recommended
width: imageData.width,
height: imageData.height
}
},
createdAt: new Date().toISOString()
});
return response;
} catch (error) {
console.error('Failed to create video post:', error);
throw error;
}
};
As with images, providing the video aspect ratio is not required but recommended.
A successful video post creation returns a detailed response:
{
"uri": "at://did:plc:abcdef123456/app.bsky.feed.post/123456789",
"cid": "bafyreib2xGD4x...",
"success": true,
"blob": {
"ref": {
"$link": "bafyreib2xGD4x..."
},
"mimeType": "video/mp4",
"size": 15678901,
"metadata": {
"duration": 45.2,
"width": 1920,
"height": 1080
}
}
}
The published video response includes:
- Standard post identifiers (
uri
andcid
). - Video blob reference for future access.
- Video metadata including duration and dimensions.
- File size and format information.
Bluesky Analytics
Getting Bluesky Account Analytics
Account analytics provide valuable insights into your Bluesky presence. The API allows you to retrieve comprehensive statistics about your linked user’s Bluesky account, including follower count, following count, total posts, and profile information. This data is particularly useful for tracking growth and engagement over time.
The following code demonstrates how to fetch account analytics. Note that the getProfile
method requires a valid user identifier (DID or handle) that was sent when logging in your user:
const getBlueskyAccountAnalytics = async (agent) => {
try {
const profile = await agent.getProfile({
actor: agent.session.did
});
return {
followers: profile.data.followersCount,
following: profile.data.followsCount,
posts: profile.data.postsCount,
description: profile.data.description
};
} catch (error) {
console.error('Failed to get analytics:', error);
throw error;
}
}
The profile analytics response provides a comprehensive overview of the account’s social presence:
{
"followers": 1250,
"following": 890,
"posts": 325,
"description": "Tech enthusiast | Developer"
}
This response gives you valuable insights:
– followers
: Total number of accounts following this profile.
– following
: Number of accounts this profile follows.
– posts
: Total number of posts made by this account.
– description
: The profile’s bio or description.
With these metrics your can:
- Track account growth over time. You’ll need to store the daily data, but after a few days you can create a chart presentation of the data.
- Measure audience engagement.
- Understand you users’ posting frequency.
- Analyze follower-to-following ratios.
Getting Bluesky Post Analytics
Individual post analytics help you understand how your published content performs on Bluesky. The API provides detailed engagement metrics for each post, including likes, reposts, and replies. This data is important for content strategy and understanding audience engagement patterns.
To retrieve analytics for a specific post, you’ll need the post’s URI. The getPostThread
method returns not just the metrics but also the full context of the post:
const getBlueskyPostAnalytics = async (agent, postUri) => {
try {
const postThread = await agent.getPostThread({
uri: postUri
});
return {
likes: postThread.data.thread.post.likeCount,
reposts: postThread.data.thread.post.repostCount,
comments: postThread.data.thread.post.replyCount,
quotes: postThread.data.thread.post.quoteCount
};
} catch (error) {
console.error('Failed to get post analytics:', error);
throw error;
}
}
The analytics response provides a detailed breakdown of engagement metrics. Here’s an example response:
{
"likes": 42,
"reposts": 15,
"comments": 8,
"quotes": 2
}
This response tells us:
- The post has received 42 likes, indicating general approval from the audience.
- It has been reposted 15 times, showing its reach beyond the original audience.
- There are 8 comments, suggesting active discussion around the content.
- The post has been quoted 8 times, meaning people like what’s been said.
These metrics can be tracked over time to understand content performance and audience engagement patterns.
Comment Management
Adding Bluesky Comments
Comments, also known as replies, in Bluesky are treated as special types of posts that are linked to a parent post. When creating a comment, you need both the URI and CID of the parent post. Comments can include all the features of regular posts, including text, images, and rich media content. Here’s how to add a comment to an existing post:
const addBlueskyComment = async (agent, postUri, text) => {
try {
const response = await agent.post({
text: text,
reply: {
root: {
uri: postUri,
cid: await agent.resolvePostCid(postUri)
}
},
createdAt: new Date().toISOString()
});
return response;
} catch (error) {
console.error('Failed to add comment:', error);
throw error;
}
};
Getting Bluesky Comments
Retrieving comments on a post involves fetching the post’s thread structure. The API allows you to specify the depth of replies to retrieve, making it possible to get either direct replies or entire conversation threads. You can also fetch metadata about the comments, such as like counts and nested reply counts.
Here’s how to retrieve comments for a specific post:
const getBlueskyComments = async (agent, postUri) => {
try {
const thread = await agent.getPostThread({
uri: postUri,
// depth: 1 // optional if you only want one comment
});
return thread.data.thread.replies || [];
} catch (error) {
console.error('Failed to get comments:', error);
throw error;
}
};
The get comment response JSON:
{
"thread": {
"post": {
"uri": "at://did:plc:abcdef123456/app.bsky.feed.post/original",
"cid": "bafyreib2xGD4x...",
"author": {
"did": "did:plc:abcdef123456",
"handle": "original.author.bsky.social",
"displayName": "Original Author",
"avatar": "https://cdn.bsky.social/avatar/123.jpg"
},
"text": "The original post content",
"createdAt": "2024-01-10T12:00:00Z",
"likeCount": 25,
"repostCount": 8,
"replyCount": 3,
"indexedAt": "2024-01-10T12:00:01Z"
},
"replies": [
{
"post": {
"uri": "at://did:plc:xyz789/app.bsky.feed.post/comment1",
"cid": "bafyreib2xGD4x...",
"author": {
"did": "did:plc:xyz789",
"handle": "commenter1.bsky.social",
"displayName": "First Commenter",
"avatar": "https://cdn.bsky.social/avatar/456.jpg"
},
"text": "My first comment",
"createdAt": "2024-01-10T15:30:00Z",
"likeCount": 5,
"replyCount": 2,
"indexedAt": "2024-01-10T15:30:01Z"
},
"replies": [
{
"post": {
"uri": "at://did:plc:123abc/app.bsky.feed.post/reply1",
"cid": "bafyreib2xGD4x...",
"author": {
"did": "did:plc:123abc",
"handle": "replier.bsky.social",
"displayName": "Reply Author"
},
"text": "My second comment",
"createdAt": "2024-01-10T16:45:00Z",
"likeCount": 2,
"replyCount": 0
}
}
]
}
]
}
}
Error Handling and Best Practices
When developing against an API one of the biggest tasks is error handling. The happy path is often easier than the long list of error scenarios.
Here are some things to watch out for.
- Rate Limiting: Implement exponential backoff for API requests. No social networks likes you burdening their system. You need to play nice to be allowed to stay in their sandbox.
- Error Handling: Use try-catch blocks for all API calls. You’re going to see a lot of undocumented errors and exceptions. After catching and logging, build in the error handling so you give nice feedback to your user. For example, you had 432 characters in your post, but only 300 are allowed.
- Validation: Verify input data before making API calls. Users appreciate a quick response, so validate before making the API calls. For example, you can validate the post character length before making the post API call.
- Logging: Maintain detailed logs for debugging. The more logs the better, because when a user has an error, but you’re logging is sparse, the amount of time you’re going to need to spend on the issue will increase.
- Security: Store sensitive credentials securely. You’ll need to store the app password so you don’t have to ask for it continuously. Be sure to encrypt the app password at reset and do frequent PEN (penetration) tests.
Using Ayrshare’s Bluesky API Integration
Ayrshare’s API provides a simplified way to interact with Bluesky alongside other social networks such as Instagram, Facebook, TikTok, and YouTube. Here’s how to use Ayrshare’s social API with Bluesky.
Authorize Bluesky
With Ayrshare Social Media API, authorizing your users with Bluesky become trivial. With one endpoint call you’ll get a URL to open for your users, which allows them to link Bluesky:
You can can make single endpoint calls with the profile key for the user.
For example, you can post a video to Bluesky via the API without needing to download the blob, upload it to Bluesky, or get the aspect ratios.
For example, here is how to publish a Bluesky post – using axios:
const axios = require('axios');
const API_KEY = "API_KEY";
const postToBlueskyViaAyrshare = async (API_KEY, content) => {
try {
const response = await axios.post('https://api.ayrshare.com/api/post', {
post:"An amazing post",
platforms: ['bluesky'],
media: ['https://example.com/video.mp4'] // Optional
}, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Porfile-Key': PROFILE_KEY,
'Content-Type': 'application/json'
}
});
return response.data;
} catch (error) {
console.error('Ayrshare post failed:', error);
throw error;
}
}
You can publish to other social networks such s YouTube, Instagram, or Facebook by including them in the platforms
array.
Response publish post example:
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/bluesky
"status": "success",
"id": "at://did:plc:n7atrjd22xgkmgwig6dzlhzd/app.bsky.feed.post/3lez7fwx452",
"cid": "bafyreie6n475cd3ynr6sfacvohu5qgjibcooxnug6zcbghkwnrwi5stafy",
"postUrl": "https://bsky.app/profile/madworlds.bsky.social/post/3lez7fwx4572",
"platform": "bluesky"
}
Or get analytics on the Bluesky post – using fetch this time:
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/analytics/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({
id: "Post ID", // required
platforms: ["bluesky", "facebook", "instagram",
"linkedin", "pinterest", "tiktok",
"twitter", "youtube"] // optional if you want to just get all
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
The JSON post analytics response:
{
"id": "at://did:plc:n7atrjd22xgkmgwig6dzlhzd/app.bsky.feed.post/3lez7fwx457",
"postUrl": "https://bsky.app/profile/madworlds.bsky.social/post/3lez7fwx457",
"analytics": {
"cid": "bafyreigh7nz7x6pl4atgsextqtcbh33mg66yqt3caeihutn7ojkmpdtnzm",
"createdAt": "2025-01-05T18:04:28.316Z",
"id": "at://did:plc:n7atrjd22xgkmgwig6dzlhzd/app.bsky.feed.post/3lfb57nxgxs2e",
"indexedAt": "2025-01-05T18:04:29.150Z",
"labels": [],
"likeCount": 3,
"post": "The day is what you make it!",
"postUrl": "https://bsky.app/profile/madworlds.bsky.social/post/3lez7fwx45723",
"quoteCount": 2,
"replyCount": 1,
"repostCount": 1,
"viewer": {
"threadMuted": false,
"embeddingDisabled": false
}
}
}
Learn more about Ayrshare’s API in our docs or website.
Next Steps
The Bluesky API provides a robust set of features for social media integration, and with the addition of Ayrshare’s API support, developers have multiple options for implementation. Whether you’re building a social media management tool, a content publishing platform, or simply want to integrate Bluesky into your existing application, the API provides the flexibility and functionality needed for sophisticated social media interactions.
Remember to stay updated with the latest changes in the AT Protocol and Bluesky API documentation, as the platform continues to evolve and add new features.
For more information and detailed documentation, visit: