Engagement
Like, repost, bookmark, and reply on Bluesky posts through Onepostly.
Bluesky engagement maps directly onto the Onepostly like, repost and bookmark surface. Every action targets a published post. Pass post as described in Analytics. Use the internal id for Onepostly posts. Use the native post URI plus accountId for outside posts.
Likes
import { Configuration, EngagementApi } from "@onepostly/sdk";
const engagementApi = new EngagementApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await engagementApi.like({
engagementTargetBody: {
"post": "1699f415-7fb6-43f4-9d2a-c447491f32a8"
},
});
console.log(response);{
"postId": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"destinationId": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"platform": "bluesky",
"kind": "like",
"active": true
}import { Configuration, EngagementApi } from "@onepostly/sdk";
const engagementApi = new EngagementApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await engagementApi.unlike({
post: "1699f415-7fb6-43f4-9d2a-c447491f32a8",
});
console.log(response);{
"postId": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"destinationId": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"platform": "bluesky",
"kind": "like",
"active": false
}Unliking returns the same shape with active: false.
Reposts
import { Configuration, EngagementApi } from "@onepostly/sdk";
const engagementApi = new EngagementApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await engagementApi.retweet({
engagementTargetBody: {
"post": "1699f415-7fb6-43f4-9d2a-c447491f32a8"
},
});
console.log(response);{
"postId": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"destinationId": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"platform": "bluesky",
"retweeted": true,
"retweetId": "1960956321831854280"
}import { Configuration, EngagementApi } from "@onepostly/sdk";
const engagementApi = new EngagementApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await engagementApi.undoRetweet({
post: "1699f415-7fb6-43f4-9d2a-c447491f32a8",
});
console.log(response);{
"postId": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"destinationId": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"platform": "bluesky",
"retweeted": false
}Undoing removes exactly the repost this connection created, so no id is needed. It returns the same shape with retweeted: false.
Bookmarks
Bookmarks are private to your account.
import { Configuration, EngagementApi } from "@onepostly/sdk";
const engagementApi = new EngagementApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await engagementApi.bookmark({
engagementTargetBody: {
"post": "1699f415-7fb6-43f4-9d2a-c447491f32a8"
},
});
console.log(response);{
"postId": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"destinationId": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"platform": "bluesky",
"kind": "bookmark",
"active": true
}import { Configuration, EngagementApi } from "@onepostly/sdk";
const engagementApi = new EngagementApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await engagementApi.removeBookmark({
post: "1699f415-7fb6-43f4-9d2a-c447491f32a8",
});
console.log(response);{
"postId": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"destinationId": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"platform": "bluesky",
"kind": "bookmark",
"active": false
}Removing a bookmark returns the same shape with active: false.
Quotes
Quote with quoteTweetId on create. See Posts → Quote.
Successful engagement calls emit the matching webhooks. Those are engagement.liked, engagement.retweeted and their undo events.
Comments
Manage replies on a published Bluesky post. Replies have the same 300 character limit as posts.
List
Returns the full reply thread of the post. Top level replies include their nested replies.
import { Configuration, CommentsApi } from "@onepostly/sdk";
const commentsApi = new CommentsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await commentsApi.listComments({
post: "1699f415-7fb6-43f4-9d2a-c447491f32a8",
limit: 25,
});
console.log(response);{
"comments": {
"post": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"subjects": [
{
"scope": "destination",
"subjectId": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "bluesky",
"externalPostId": "17895695668004550",
"status": "ready",
"comments": [
{
"id": "c1",
"text": "Love this!",
"username": "jane_doe",
"likeCount": 12,
"timestamp": "2026-08-25T10:05:00.000Z",
"parentId": null
}
],
"nextCursor": null,
"error": null,
"externalUrl": "https://bsky.app/profile/you/post/17895695668004550"
}
]
}
}| Query | Required | Notes |
|---|---|---|
post | Yes | Internal post id or native post URI. Native ids require accountId |
accountId | For native ids | Disambiguates the target account. Also narrows internal posts with several destinations |
destinationId | No | Globally unique. Resolves on its own or narrows one destination inside an internal post |
limit | No | 1-50, default 25 |
cursor | No | Not used. The full thread is returned |
Create
Creates a reply to the post, or a nested reply when parentCommentId is set. Mentions, links and hashtags are detected automatically.
import { Configuration, CommentsApi } from "@onepostly/sdk";
const commentsApi = new CommentsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await commentsApi.createComment({
createCommentBody: {
"post": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"text": "Love this!",
"parentCommentId": "c0"
},
});
console.log(response);{
"comment": {
"id": "c1",
"text": "Love this!",
"username": "jane_doe",
"likeCount": 12,
"timestamp": "2026-08-25T10:05:00.000Z"
}
}| Body | Required | Notes |
|---|---|---|
post | Yes | Internal post id or native post URI. Native ids require accountId |
text | Yes | Max 300 characters |
accountId | For native ids | Target account for native ids. Also disambiguates internal posts |
destinationId | No | Globally unique. Resolves on its own or targets one destination inside an internal post |
parentCommentId | No | at URI of the reply to nest under. Omit to reply to the post |
Delete
Deletes a reply owned by the connected account. commentId is the reply at URI on Bluesky.
import { Configuration, CommentsApi } from "@onepostly/sdk";
const commentsApi = new CommentsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await commentsApi.deleteComment({
post: "1699f415-7fb6-43f4-9d2a-c447491f32a8",
commentId: "c0",
});
console.log(response);{
"deleted": {
"postId": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"destinationId": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"platform": "bluesky",
"commentId": "c0",
"deleted": true
}
}Only your own replies can be deleted.
Outside Posts
Posts published outside Onepostly work the same way once they are synced. Sync by native post id then use that id plus accountId in every comments call. Omit url and postId to refresh the recent posts of the account in bulk.
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.syncExternal({
syncExternalBody: {
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"postId": "at://did:plc:alice/app.bsky.feed.post/abc123"
},
});
console.log(response);{
"synced": {
"postsFound": 1,
"postsSynced": 1,
"skipped": false
},
"found": true,
"post": {
"id": "e8f9a0b1-c2d3-4e5f-8a6b-c7d8e9f0a1b2",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "bluesky",
"externalPostId": "at://did:plc:alice/app.bsky.feed.post/abc123",
"externalUrl": "https://bsky.app/profile/did:plc:alice/post/abc123",
"mediaKind": "text",
"publishedAt": "2026-08-25T10:00:00.000Z",
"syncedAt": "2026-08-25T10:00:00.000Z",
"analyticsStatus": "ready"
}
}import { Configuration, CommentsApi } from "@onepostly/sdk";
const commentsApi = new CommentsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await commentsApi.listComments({
post: "1699f415-7fb6-43f4-9d2a-c447491f32a8",
limit: 25,
});
console.log(response);{
"comments": {
"post": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"subjects": [
{
"scope": "destination",
"subjectId": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "bluesky",
"externalPostId": "17895695668004550",
"status": "ready",
"comments": [
{
"id": "c1",
"text": "Love this!",
"username": "jane_doe",
"likeCount": 12,
"timestamp": "2026-08-25T10:05:00.000Z",
"parentId": null
}
],
"nextCursor": null,
"error": null,
"externalUrl": "https://bsky.app/profile/you/post/17895695668004550"
}
]
}
}Errors
| Code | Cause | Fix |
|---|---|---|
MISSING_EXTERNAL_ID | Destination not published yet | Wait for publish |
QUOTA_EXCEEDED | The platform's capacity for this action is spent right now | Retry in a few hours |
ENGAGEMENT_PERMISSION | App Password lacks access | Create a new App Password and reconnect |
LIKE_FAILED or UNLIKE_FAILED | Bluesky rejected the action | Check the error message |
RETWEET_FAILED or UNRETWEET_FAILED | Bluesky rejected the repost | Check the error message |
TEXT_REQUIRED | Empty reply text | Add message text |
TEXT_TOO_LONG | Over 300 characters | Trim the text |
COMMENT_CREATE_FAILED | Bluesky rejected the reply | Check the error message |
TOKEN_INVALID | App Password revoked or session expired | Create a new App Password and reconnect |