Engagement
Repost Threads posts and manage replies through Onepostly.
Threads engagement is reposts. Onepostly maps the X style retweet endpoints onto Threads reposts, so the same integration works on both platforms. Pass post as described in Analytics. Use the internal id for Onepostly posts. Use the native post id plus accountId for outside posts.
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": "threads",
"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": "threads",
"retweeted": false
}Undoing removes exactly the repost this connection created, so no id is needed. Pass the original post. It returns the same shape with retweeted: false.
Successful calls emit the engagement.retweeted and engagement.unretweeted webhooks.
Quotes
Quote a published Threads post by passing quoteTweetId on create. See Posts → Quote. Quoting emits engagement.quoted plus the usual publish events.
Likes and bookmarks do not exist on Threads.
Comments
Manage replies on a published Threads post. Replying works on your own connected account posts.
List
Returns the post replies. 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": "threads",
"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://www.threads.com/@you/post/17895695668004550"
}
]
}
}| Query | Required | Notes |
|---|---|---|
post | Yes | Internal post id or native post id. 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 | Pagination cursor from the previous response nextCursor. Requires a single resolved target |
Create
Creates a top level reply, or a nested reply when parentCommentId is set.
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 id. Native ids require accountId |
text | Yes | Max 500 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 | Nest the reply under another reply. Omit to reply to the post itself |
Delete
Deletes a reply owned by the connected account. commentId is the reply id on Threads. Get it from List above (comments[].id). The same id works for hide below.
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": "threads",
"commentId": "c0",
"deleted": true
}
}Hide or unhide
Hides a reply so only its author still sees it, or reveals it again with hidden: false.
import { Configuration, CommentsApi } from "@onepostly/sdk";
const commentsApi = new CommentsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await commentsApi.hideComment({
body: {
"post": "1699f415-7fb6-43f4-9d2a-c447491f32a8",
"commentId": "c0",
"hidden": true
},
});
console.log(response);{
"hidden": {
"postId": "string",
"destinationId": "string",
"platform": "threads",
"commentId": "string",
"hidden": false
}
}If a call fails with a permission error, reconnect the Threads connection once.
Outside Posts
Posts published outside Onepostly work the same way once they are synced. Sync by URL then use the native 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",
"url": "https://www.threads.com/@you/post/DbNQAb8ABC1"
},
});
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": "threads",
"externalPostId": "DbNQAb8ABC1",
"externalUrl": "https://www.threads.com/@you/post/DbNQAb8ABC1",
"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": "threads",
"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://www.threads.com/@you/post/17895695668004550"
}
]
}
}Errors
| Code | Cause | Fix |
|---|---|---|
MISSING_EXTERNAL_ID | Destination not published yet | Wait for publish |
ENGAGEMENT_PERMISSION | Connection lacks the repost permission | Reconnect the account |
RETWEET_FAILED or UNRETWEET_FAILED | Threads rejected the action | Check the error message for the Threads reason |
COMMENTS_PERMISSION | Connection lacks threads_manage_replies | Reconnect the account |
TEXT_REQUIRED | Empty reply text | Add message text |
TEXT_TOO_LONG | Over 500 characters | Trim the text |
COMMENT_CREATE_FAILED | Threads rejected the reply | Check the error message for the Threads reason |
COMMENT_HIDE_FAILED | Threads rejected the hide toggle | Check the error message for the Threads reason |