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"
      }
    ]
  }
}
QueryRequiredNotes
postYesInternal post id or native post URI. Native ids require accountId
accountIdFor native idsDisambiguates the target account. Also narrows internal posts with several destinations
destinationIdNoGlobally unique. Resolves on its own or narrows one destination inside an internal post
limitNo1-50, default 25
cursorNoNot 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"
  }
}
BodyRequiredNotes
postYesInternal post id or native post URI. Native ids require accountId
textYesMax 300 characters
accountIdFor native idsTarget account for native ids. Also disambiguates internal posts
destinationIdNoGlobally unique. Resolves on its own or targets one destination inside an internal post
parentCommentIdNoat 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

CodeCauseFix
MISSING_EXTERNAL_IDDestination not published yetWait for publish
QUOTA_EXCEEDEDThe platform's capacity for this action is spent right nowRetry in a few hours
ENGAGEMENT_PERMISSIONApp Password lacks accessCreate a new App Password and reconnect
LIKE_FAILED or UNLIKE_FAILEDBluesky rejected the actionCheck the error message
RETWEET_FAILED or UNRETWEET_FAILEDBluesky rejected the repostCheck the error message
TEXT_REQUIREDEmpty reply textAdd message text
TEXT_TOO_LONGOver 300 charactersTrim the text
COMMENT_CREATE_FAILEDBluesky rejected the replyCheck the error message
TOKEN_INVALIDApp Password revoked or session expiredCreate a new App Password and reconnect

See Also

  • Posts
  • Analytics
  • Webhooks for comment.created, comment.deleted, engagement.liked, engagement.retweeted and engagement.bookmarked events