Platforms
Threads
Connect a Threads profile, publish posts, reply chains, replies and quotes, manage replies and reposts, and read Threads post metrics.
Publish posts and reply chains, manage replies and reposts, and read metrics.
Quick Reference
| Property | Value |
|---|---|
| Account types | Threads profiles |
| Connection method | OAuth |
| Post types | text, image, multi_image, video |
| Max media per post | 20 |
| Text limit | 500 characters |
| Threads | |
| Quotes | |
| Remote delete | Whole thread |
| Comments | |
| Likes | No |
| Reposts | |
| Bookmarks | No |
| Direct messages | No |
| Post analytics | |
| Account analytics | No |
Each post, reply, thread item and firstComment has its own 500 limit.
Connect
Connect a profile in Dashboard → Connections, or with a connect session.
Permissions
| Scope | Used for |
|---|---|
threads_basic | Id, username and avatar |
threads_content_publish | Posts, replies, quotes and reposts |
threads_read_replies | Listing replies |
threads_manage_replies | Writing and hiding replies |
threads_manage_insights | Analytics |
threads_delete | Deleting posts and undoing reposts |
Staying Connected
Access lasts about 60 days and renews itself, so canAutoRenew is true. If it expires or the owner removes the app, you get TOKEN_INVALID. Reconnect then, or on a *_PERMISSION code. The id stays the same.
Publish
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Launch week recap.",
"mediaKind": "multi_image",
"media": [
{
"url": "https://cdn.onepostly.com/media/launch-1.jpg"
},
{
"url": "https://cdn.onepostly.com/media/launch-2.jpg"
},
{
"url": "https://cdn.onepostly.com/media/launch-demo.mp4"
}
],
"accounts": [
{
"accountId": "acc_YOUR_THREADS_ACCOUNT_ID"
}
]
},
});
console.log(response);{
"groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
"data": [
{
"id": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
"groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
"accountId": "acc_YOUR_THREADS_ACCOUNT_ID",
"platform": "threads",
"source": "onepostly",
"status": "publishing",
"text": "Launch week recap.",
"mediaKind": "multi_image",
"media": [
{
"url": "https://cdn.onepostly.com/media/launch-1.jpg"
},
{
"url": "https://cdn.onepostly.com/media/launch-2.jpg"
},
{
"url": "https://cdn.onepostly.com/media/launch-demo.mp4"
}
],
"thread": [],
"scheduledFor": null,
"timezone": null,
"options": {},
"platformPostId": null,
"url": null,
"error": null,
"publishedAt": null,
"metrics": null,
"createdAt": "2027-03-15T09:00:00.000Z",
"updatedAt": "2027-03-15T09:00:00.000Z"
}
]
}Once published, platformPostId is the Threads post id and url its link.
Post Types
mediaKind | Publishes | Media |
|---|---|---|
text | Text post | None, text required |
image | Image post | 1 image |
multi_image | Carousel | 2 to 20 images or videos, mixed |
video | Video post | 1 video |
stories returns 400 VALIDATION_ERROR. Without mediaKind, the files decide. A URL ending in .mp4, .mov, .m4v, .webm or .avi is a video.
Threads
Add thread for 1 to 25 replies, each under the one before. The top-level text and media are the first post. See Threads, Replies and Quotes.
| Rule | Detail |
|---|---|
| Reply text | Up to 500, empty only with media |
| Reply media | 1 item, or a carousel of 2 to 20 |
| Checked first | Every post and firstComment, before anything goes live |
| Accounts | All on X, Threads or Bluesky |
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "How we cut our build time in half.",
"mediaKind": "image",
"media": [
{
"url": "https://cdn.onepostly.com/media/build-chart.png"
}
],
"thread": [
{
"text": "1/ We cached dependencies between runs."
},
{
"text": "2/ Then we split tests across 4 machines.",
"media": [
{
"url": "https://cdn.onepostly.com/media/split-1.png"
},
{
"url": "https://cdn.onepostly.com/media/split-2.png"
}
]
}
],
"accounts": [
{
"accountId": "acc_YOUR_THREADS_ACCOUNT_ID"
}
]
},
});
console.log(response);{
"groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
"data": [
{
"id": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
"groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
"accountId": "acc_YOUR_THREADS_ACCOUNT_ID",
"platform": "threads",
"source": "onepostly",
"status": "publishing",
"text": "How we cut our build time in half.",
"mediaKind": "image",
"media": [
{
"url": "https://cdn.onepostly.com/media/build-chart.png"
}
],
"thread": [
{
"text": "1/ We cached dependencies between runs.",
"media": []
},
{
"text": "2/ Then we split tests across 4 machines.",
"media": [
{
"url": "https://cdn.onepostly.com/media/split-1.png"
},
{
"url": "https://cdn.onepostly.com/media/split-2.png"
}
]
}
],
"scheduledFor": null,
"timezone": null,
"options": {},
"platformPostId": null,
"url": null,
"error": null,
"publishedAt": null,
"metrics": null,
"createdAt": "2027-03-15T09:00:00.000Z",
"updatedAt": "2027-03-15T09:00:00.000Z"
}
]
}platformPostId and url point to the first post. If a reply fails, earlier posts stay live and the post ends failed.
Replies and Quotes
threads.replyToPostId replies to a post, and threads.quotePostId quotes one. Both take your pst_ id or a Threads post id. With thread, the chain goes under the new reply, not under the post it replies to.
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "This is the best explanation of rate limits I have read.",
"accounts": [
{
"accountId": "acc_YOUR_THREADS_ACCOUNT_ID",
"threads": {
"quotePostId": "18047312345678901"
}
}
]
},
});
console.log(response);{
"groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
"data": [
{
"id": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
"groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
"accountId": "acc_YOUR_THREADS_ACCOUNT_ID",
"platform": "threads",
"source": "onepostly",
"status": "publishing",
"text": "This is the best explanation of rate limits I have read.",
"mediaKind": "text",
"media": [],
"thread": [],
"scheduledFor": null,
"timezone": null,
"options": {
"threads": {
"quotePostId": "18047312345678901"
}
},
"platformPostId": null,
"url": null,
"error": null,
"publishedAt": null,
"metrics": null,
"createdAt": "2027-03-15T09:00:00.000Z",
"updatedAt": "2027-03-15T09:00:00.000Z"
}
]
}A pst_ id not live yet returns 409 POST_NOT_PUBLISHED.
These replies send post webhooks. Comment replies send comment.created.
Options
Options go under threads. Thread replies go in the top-level thread.
Prop
Type
Topic Tag
topicTag replaces the topic Threads takes from a hashtag. It applies only to the first post, and is ignored on a reply.
A tag with . or & fails with INVALID_TOPIC_TAG.
First Comment
firstComment posts after the post and its thread replies go live. If Threads refuses it, the post still publishes. Deleting the post keeps it.
Media Requirements
These are Threads' limits, on top of the Media URL Requirements.
| Media | Formats | Max size |
|---|---|---|
| Images | JPEG and PNG | 8 MB |
| Videos | MP4 and MOV | 1 GB |
A file outside these formats and sizes is refused. See Platform Limits.
Images
| Property | Requirement |
|---|---|
| Width | 320 to 1440 |
| Aspect ratio | Up to 10:1 |
Videos
| Property | Requirement |
|---|---|
| Codec | H.264 or HEVC, AAC |
| Duration | Up to 5 minutes |
| Max width | 1920 |
| Frame rate | 23 to 60 fps |
| Aspect ratio | 9:16 recommended |
Scheduling, Editing and Deleting
Scheduling and editing work as usual. Published posts cannot be edited.
Deleting removes the whole thread, replies first, but not firstComment. A partial delete returns THREAD_DELETE_PARTIAL, so send it again. A post made outside Onepostly is deleted on its own, without its replies.
Threads Limits
| Action | Per profile in 24 hours |
|---|---|
| Posts | 250 |
| Replies | 1,000 |
| Deletions | 100 |
Past one, you get QUOTA_EXCEEDED. Publishing limits apply too.
Engagement
See Comments and Likes and Reposts for the requests.
| Action | Supported | Notes |
|---|---|---|
| List comments | Replies to the post, newest first | |
| Comment or reply | Up to 500 characters | |
| Delete a comment | Only the profile's own replies | |
| Hide or unhide | Replies under it follow it | |
| Repost | Any Threads post | |
| Undo a repost | See Undo a Repost | |
| Quote | See Replies and Quotes | |
| Like, bookmark, list reposters | No | UNSUPPORTED_PLATFORM |
| Like a comment, private reply | No | UNSUPPORTED_PLATFORM |
Name a post by its pst_ id or Threads id. Another account's pst_ id returns POST_ACCOUNT_MISMATCH, so use the Threads id.
Comments
On Threads, comments are replies, each with a cmt_ id. The list skips replies to replies, so parentId is always null there.
You cannot delete other people's replies, but you can hide them.
Undo a Repost
Threads undoes a repost only by the repost's own id. So it works only on reposts made here, of this profile's Onepostly posts. Others fail with UNREPOST_REQUIRES_REPOST_ID, so remove them in the Threads app.
import { Configuration, EngagementApi } from "@onepostly/sdk";
const engagementApi = new EngagementApi(
new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);
const response = await engagementApi.repostPost({
id: "acc_YOUR_THREADS_ACCOUNT_ID",
postId: "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
});
console.log(response);{
"postId": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
"accountId": "acc_YOUR_THREADS_ACCOUNT_ID",
"platform": "threads",
"reposted": true,
"platformRepostId": "18062934718562044"
}Inbox
Threads has no inbox through Onepostly.
Analytics
Metrics
| Field | Threads metric |
|---|---|
impressions | views |
reach | Always null |
views | views |
likes | likes |
comments | replies |
shares | shares, else reposts + quotes |
saves | Always null |
engagement | likes + comments + shares |
A missing metric is null, not 0.
Read Post Analytics
import { Configuration, AnalyticsApi } from "@onepostly/sdk";
const analyticsApi = new AnalyticsApi(
new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);
const response = await analyticsApi.getPostAnalytics({
id: "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
});
console.log(response);{
"postId": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
"accountId": "acc_YOUR_THREADS_ACCOUNT_ID",
"platform": "threads",
"status": "ready",
"metrics": {
"impressions": 1234,
"reach": null,
"likes": 123,
"comments": 12,
"shares": 4,
"saves": null,
"views": 1234,
"engagement": 139
},
"fetchedAt": "2027-03-15T10:00:00.000Z",
"error": null
}Posts made outside work once synced. Freshness is the standard. A fresh post reads pending. A deleted one reads unavailable with POST_NOT_FOUND.
List Account Media lists recent posts. Its metrics are null.
Limitations
Not available for Threads through Onepostly.
- Editing a published post
- Stories
- Likes, bookmarks and reposter lists
- Undoing reposts of outside posts
- Replies to replies in the list
- Liking replies and private replies
- Direct messages
- Account stats and insights
Errors
A request that breaks a rule on this page returns 400 VALIDATION_ERROR and creates nothing. Every code is in Errors.
Publish Errors
| Code | Cause | Fix |
|---|---|---|
POST_NOT_PUBLISHED | 409. The pst_ reply or quote target is not live | Wait, or use its Threads id |
INVALID_MULTI_IMAGE | Over 20 carousel items | Send 2 to 20 |
INVALID_TOPIC_TAG | . or & in topicTag | Remove them |
CONTAINER_FAILED | Threads could not process the media | See Media Requirements |
QUOTA_EXCEEDED | A Threads limit | Schedule it later |
THREAD_DELETE_PARTIAL | Some replies were not deleted | Send the delete again |
DELETE_PERMISSION | No threads_delete | Reconnect with every permission |
Engagement Errors
| Code | Cause | Fix |
|---|---|---|
POST_ACCOUNT_MISMATCH | Another account's pst_ id | Use the Threads id |
UNREPOST_REQUIRES_REPOST_ID | The repost was not made here | Remove it in the app |
COMMENT_DELETE_FAILED | Not the profile's own reply | Hide it instead |
COMMENT_HIDE_FAILED | The reply is gone, or not on the post | Check the reply |
COMMENTS_UNSUPPORTED | Replies cannot be listed | Check the post exists |
Analytics Errors
| Code | Cause | Fix |
|---|---|---|
ANALYTICS_PENDING | No metrics yet | Read again later |
POST_NOT_FOUND | Deleted on Threads | Check the post |
ANALYTICS_PERMISSION | No threads_manage_insights | Reconnect with every permission |
Connect a Facebook Page, publish text, photo, album, video, Reel and Story posts, manage comments and Messenger conversations, and read post and Page analytics.
Connect a LinkedIn personal profile, publish text, images, video and document posts with a first comment, and keep the account connected.