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

PropertyValue
Account typesThreads profiles
Connection methodOAuth
Post typestext, image, multi_image, video
Max media per post20
Text limit500 characters
Threads
Quotes
Remote deleteWhole thread
Comments
LikesNo
Reposts
BookmarksNo
Direct messagesNo
Post analytics
Account analyticsNo

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

ScopeUsed for
threads_basicId, username and avatar
threads_content_publishPosts, replies, quotes and reposts
threads_read_repliesListing replies
threads_manage_repliesWriting and hiding replies
threads_manage_insightsAnalytics
threads_deleteDeleting 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

mediaKindPublishesMedia
textText postNone, text required
imageImage post1 image
multi_imageCarousel2 to 20 images or videos, mixed
videoVideo post1 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.

RuleDetail
Reply textUp to 500, empty only with media
Reply media1 item, or a carousel of 2 to 20
Checked firstEvery post and firstComment, before anything goes live
AccountsAll 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.

MediaFormatsMax size
ImagesJPEG and PNG8 MB
VideosMP4 and MOV1 GB

A file outside these formats and sizes is refused. See Platform Limits.

Images

PropertyRequirement
Width320 to 1440
Aspect ratioUp to 10:1

Videos

PropertyRequirement
CodecH.264 or HEVC, AAC
DurationUp to 5 minutes
Max width1920
Frame rate23 to 60 fps
Aspect ratio9: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

ActionPer profile in 24 hours
Posts250
Replies1,000
Deletions100

Past one, you get QUOTA_EXCEEDED. Publishing limits apply too.

Engagement

See Comments and Likes and Reposts for the requests.

ActionSupportedNotes
List commentsReplies to the post, newest first
Comment or replyUp to 500 characters
Delete a commentOnly the profile's own replies
Hide or unhideReplies under it follow it
RepostAny Threads post
Undo a repostSee Undo a Repost
QuoteSee Replies and Quotes
Like, bookmark, list repostersNoUNSUPPORTED_PLATFORM
Like a comment, private replyNoUNSUPPORTED_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

FieldThreads metric
impressionsviews
reachAlways null
viewsviews
likeslikes
commentsreplies
sharesshares, else reposts + quotes
savesAlways null
engagementlikes + 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

CodeCauseFix
POST_NOT_PUBLISHED409. The pst_ reply or quote target is not liveWait, or use its Threads id
INVALID_MULTI_IMAGEOver 20 carousel itemsSend 2 to 20
INVALID_TOPIC_TAG. or & in topicTagRemove them
CONTAINER_FAILEDThreads could not process the mediaSee Media Requirements
QUOTA_EXCEEDEDA Threads limitSchedule it later
THREAD_DELETE_PARTIALSome replies were not deletedSend the delete again
DELETE_PERMISSIONNo threads_deleteReconnect with every permission

Engagement Errors

CodeCauseFix
POST_ACCOUNT_MISMATCHAnother account's pst_ idUse the Threads id
UNREPOST_REQUIRES_REPOST_IDThe repost was not made hereRemove it in the app
COMMENT_DELETE_FAILEDNot the profile's own replyHide it instead
COMMENT_HIDE_FAILEDThe reply is gone, or not on the postCheck the reply
COMMENTS_UNSUPPORTEDReplies cannot be listedCheck the post exists

Analytics Errors

CodeCauseFix
ANALYTICS_PENDINGNo metrics yetRead again later
POST_NOT_FOUNDDeleted on ThreadsCheck the post
ANALYTICS_PERMISSIONNo threads_manage_insightsReconnect with every permission