# Threads

Publish a chain of connected posts on X, Threads and Bluesky with threadItems, each with its own text and media.

A thread is a chain of posts where each one replies to the one before it. X, Threads and Bluesky support them through the same field: `threadItems` in that entry's `platformSpecificData`. Each item has its own `content` and optional `mediaItems`.

## Publish a thread on X

```bash cURL
curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "What we learned moving 2 million jobs to a new queue",
    "scheduledFor": "2026-11-04T15:00:00Z",
    "platforms": [{
      "platform": "twitter",
      "accountId": "6710c3e2a9f14b0d8e5c7a23",
      "platformSpecificData": {
        "threadItems": [
          {
            "content": "We moved 2 million background jobs to a new queue last month. Here is what broke, and what we would do again. 🧵",
            "mediaItems": [{ "type": "image", "url": "https://pozy.ai/media/temp/1730289600_t4h1_queue-graph.png" }]
          },
          { "content": "1. Retries were the real load. 40% of our traffic was jobs retrying the same failure." },
          { "content": "2. Idempotency keys on every job made the cutover boring, which is the goal." },
          { "content": "3. Ship the dashboard before the migration, not after. Full write-up: https://example.com/blog/queues" }
        ]
      }
    }]
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    content: "What we learned moving 2 million jobs to a new queue",
    scheduledFor: "2026-11-04T15:00:00Z",
    platforms: [
      {
        platform: "twitter",
        accountId: "6710c3e2a9f14b0d8e5c7a23",
        platformSpecificData: {
          threadItems: [
            {
              content: "We moved 2 million background jobs to a new queue last month. Here is what broke, and what we would do again. 🧵",
              mediaItems: [{ type: "image", url: "https://pozy.ai/media/temp/1730289600_t4h1_queue-graph.png" }],
            },
            { content: "1. Retries were the real load. 40% of our traffic was jobs retrying the same failure." },
            { content: "2. Idempotency keys on every job made the cutover boring, which is the goal." },
            { content: "3. Ship the dashboard before the migration, not after. Full write-up: https://example.com/blog/queues" },
          ],
        },
      },
    ],
  }),
});
if (!res.ok) throw new Error((await res.json()).error);
```
```python Python
requests.post(
    "https://api.pozy.ai/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "content": "What we learned moving 2 million jobs to a new queue",
        "scheduledFor": "2026-11-04T15:00:00Z",
        "platforms": [{
            "platform": "twitter",
            "accountId": "6710c3e2a9f14b0d8e5c7a23",
            "platformSpecificData": {
                "threadItems": [
                    {
                        "content": "We moved 2 million background jobs to a new queue last month. Here is what broke, and what we would do again. 🧵",
                        "mediaItems": [{"type": "image", "url": "https://pozy.ai/media/temp/1730289600_t4h1_queue-graph.png"}],
                    },
                    {"content": "1. Retries were the real load. 40% of our traffic was jobs retrying the same failure."},
                    {"content": "2. Idempotency keys on every job made the cutover boring, which is the goal."},
                    {"content": "3. Ship the dashboard before the migration, not after. Full write-up: https://example.com/blog/queues"},
                ]
            },
        }],
    },
    timeout=30,
).raise_for_status()
```

> **The top-level content is not published:**
> 
> When an entry has `threadItems`, its first post is `threadItems[0]`. The top-level `content` is kept for the dashboard, search and your own lists, but it is not posted on that platform. Put the opening post in the first item.

The entry's `platformPostUrl` points at the first post of the thread.

## Rules per platform

| | X (`twitter`) | Threads | Bluesky |
| --- | --- | --- | --- |
| Field | `threadItems` | `threadItems` | `threadItems` |
| Text per item | 280 characters on standard accounts, more with a paid X subscription | 500 characters | 300 characters |
| Media per item | Up to 4 images, or 1 video, or 1 GIF | Images, a video or a carousel | Up to 4 images or 1 video |
| Extras | A thread cannot carry a `poll`. Add `replyToTweetId` to post the whole thread as a reply. | `firstComment` replies to the first post of the thread. `topic_tag` applies to the thread. | `langs` applies to every item. |
| Edit after publishing | Not for threads | No edit support | No edit support |

X counts characters with weights: every link counts as 23 characters and most emoji count as 2. Check a long thread with `POST /v1/tools/validate/post-length` before scheduling ([Validation](https://pozy.ai/docs/publishing/validation)).

## One thread, three platforms

Each entry carries its own `threadItems`, so you can split the same story differently for each platform's limit. A shorter LinkedIn version can sit next to them as a single post:

```json
{
  "content": "Our queue migration, in short",
  "platforms": [
    {
      "platform": "twitter",
      "accountId": "6710c3e2a9f14b0d8e5c7a23",
      "platformSpecificData": {
        "threadItems": [
          { "content": "We moved 2 million jobs to a new queue. What broke, in three posts." },
          { "content": "Retries were 40% of the load." },
          { "content": "Idempotency keys made the cutover boring." }
        ]
      }
    },
    {
      "platform": "threads",
      "accountId": "6710c3e2a9f14b0d8e5c7a2a",
      "platformSpecificData": {
        "threadItems": [
          { "content": "We moved 2 million jobs to a new queue. Retries turned out to be 40% of the load." },
          { "content": "Idempotency keys on every job made the cutover boring, which is exactly what you want." }
        ]
      }
    },
    {
      "platform": "bluesky",
      "accountId": "6710c3e2a9f14b0d8e5c7a2b",
      "platformSpecificData": {
        "langs": ["en"],
        "threadItems": [
          { "content": "We moved 2 million jobs to a new queue. What broke:" },
          { "content": "Retries were 40% of the load. Idempotency keys made the cutover boring." }
        ]
      }
    },
    {
      "platform": "linkedin",
      "accountId": "6710c3e2a9f14b0d8e5c7a21",
      "customContent": "We moved 2 million background jobs to a new queue. Two lessons: retries were 40% of the load, and idempotency keys made the cutover boring. Full write-up on the blog."
    }
  ]
}
```

## Reply to an existing post on X

To answer a post instead of starting a new thread, set `replyToTweetId` to the id of the post you are answering. With `threadItems` as well, the whole thread hangs under that post. X only accepts replies to your own posts or to posts that mention you, and `replyToTweetId` cannot be combined with `replySettings`. To quote a post instead, use `quoteTweetId`. See [X](https://pozy.ai/docs/platforms/x).

## Taking a thread down

`POST /v1/posts/{postId}/unpublish` with `"platform": "twitter"`, `"threads"` or `"bluesky"` deletes every post of that thread on that platform. See [Edit and unpublish](https://pozy.ai/docs/publishing/editing).

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`POST /v1/posts`](https://pozy.ai/docs/api/createPost) | Create post |
| [`POST /v1/tools/validate/post-length`](https://pozy.ai/docs/api/validatePostLength) | Validate character count |
| [`POST /v1/tools/validate/post`](https://pozy.ai/docs/api/validatePost) | Validate post content |
| [`POST /v1/posts/{postId}/unpublish`](https://pozy.ai/docs/api/unpublishPost) | Unpublish post |
