> ## Documentation Index
> Fetch the complete documentation index at: https://docs.postbreeze.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Threads

> Schedule Threads posts — text, image, video, carousel (up to 20 mixed items), plus multi-post threads.

## Quick reference

| Field                   | Value                                                                 |
| ----------------------- | --------------------------------------------------------------------- |
| Character limit         | 500 characters (per post — applies to root and every thread part)     |
| Carousel size           | 2–20 items                                                            |
| Mixed media in carousel | ✅ (image + video)                                                     |
| Images per post         | 1–20                                                                  |
| Videos per post         | 1 (single video post) or any subset of a carousel                     |
| Image formats           | JPEG, PNG, WebP                                                       |
| Video formats           | MP4, MOV                                                              |
| Max image size          | 8 MB                                                                  |
| Max video size          | 1 GB                                                                  |
| Max video duration      | 5 minutes                                                             |
| Post types              | Text, Image, Video, Carousel                                          |
| Multi-post threads      | ✅ Up to 25 text-only follow-ups via `threadParts`                     |
| First comment           | ✅ (posts as a reply to the **root** post via the standard reply flow) |
| Account types           | Threads accounts (linked to Instagram)                                |

## Before you start

<Warning>
  Threads is a separate Meta product from Instagram with **its own host** — OAuth lives at `https://www.threads.net/oauth/authorize`, and the API at `https://graph.threads.net`. Don't route through `graph.facebook.com` even though Instagram does — Threads endpoints 404 there.

  Threads scopes must be comma-separated in the authorize URL; Postbreeze handles this automatically.
</Warning>

Required scopes:

* `threads_basic` — read profile.
* `threads_content_publish` — publish threads.
* `threads_manage_replies` + `threads_read_replies` — first-comment + inbox replies.
* `threads_manage_insights` — analytics.

All require Meta App Review + Business Verification before public users can grant them.

## Quick start

Workspace is inferred from your API key — no `workspaceId` argument.
Use the **flat shape** (`content` + `platforms`) for the common case
— it defaults to a text post when you skip `mediaItems`.

```ts SDK theme={null}
import Postbreeze from "@postbreeze/node";

const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY! });

await postbreeze.posts.create({
  content: "Threads is hitting a different stride lately. Anyone else feel it?",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  platforms: [{ accountId: "soc_threads_…" }],
});
```

See [Platform settings → Threads](/concepts/platform-settings#threads) for the full options reference and [Media uploads](/concepts/media-uploads) for `mediaItems` vs `mediaIds`.

## Content types

### Text-only

`kind: "TEXT"`. Uses the `auto_publish_text=true` single-step flow on Threads. No media.

<CodeGroup>
  ```js Node.js theme={null}
  const post = await postbreeze.posts.create({
    content: "Threads is hitting a different stride lately. Anyone else feel it?",
    scheduledFor: new Date(Date.now() + 30_000).toISOString(),
    platforms: [{
      accountId: "soc_threads_…",
      platformOptions: { platform: "THREADS", kind: "TEXT" },
    }],
  });
  ```

  ```python Python theme={null}
  import os
  from datetime import datetime, timedelta, timezone
  import requests

  API = "https://api.postbreeze.ai/api/v1"

  res = requests.post(
      f"{API}/posts",
      headers={"Authorization": f"Bearer {os.environ['POSTBREEZE_API_KEY']}"},
      json={
          "content": "Threads is hitting a different stride lately. Anyone else feel it?",
          "scheduledFor": (datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat(),
          "platforms": [{
              "accountId": "soc_threads_…",
              "platformOptions": {"platform": "THREADS", "kind": "TEXT"},
          }],
      },
  )
  res.raise_for_status()
  ```

  ```bash curl theme={null}
  curl -X POST https://api.postbreeze.ai/api/v1/posts \
    -H "Authorization: Bearer $POSTBREEZE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "content": "Threads is hitting a different stride lately. Anyone else feel it?",
      "scheduledFor": "2026-06-02T12:00:30Z",
      "platforms": [{
        "accountId": "soc_threads_…",
        "platformOptions": { "platform": "THREADS", "kind": "TEXT" }
      }]
    }'
  ```
</CodeGroup>

### Single image

`kind: "IMAGE"` with one image attached via `mediaIds` (or one entry in `mediaItems`).

### Single video

`kind: "VIDEO"` with one video. Up to 5 minutes, 1 GB. Threads polls processing status; Postbreeze waits \~30s before declaring success.

### Carousel (2–20 items, mixed media)

`kind: "CAROUSEL"` with 2–20 media items. **Threads is one of the few platforms that allows mixed image + video in a single carousel** — feel free to combine.

<CodeGroup>
  ```js Node.js theme={null}
  const post = await postbreeze.posts.create({
    content: "Behind the scenes from this week's shoot — slide for the video 👇",
    mediaIds: ["med_img_1", "med_img_2", "med_video_1", "med_img_3"],
    scheduledFor: new Date(Date.now() + 30_000).toISOString(),
    platforms: [{
      accountId: "soc_threads_…",
      platformOptions: { platform: "THREADS", kind: "CAROUSEL" },
    }],
  });
  ```

  ```python Python theme={null}
  res = requests.post(
      f"{API}/posts",
      headers={"Authorization": f"Bearer {os.environ['POSTBREEZE_API_KEY']}"},
      json={
          "content": "Behind the scenes from this week's shoot — slide for the video 👇",
          "mediaIds": ["med_img_1", "med_img_2", "med_video_1", "med_img_3"],
          "scheduledFor": (datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat(),
          "platforms": [{
              "accountId": "soc_threads_…",
              "platformOptions": {"platform": "THREADS", "kind": "CAROUSEL"},
          }],
      },
  )
  res.raise_for_status()
  ```

  ```bash curl theme={null}
  curl -X POST https://api.postbreeze.ai/api/v1/posts \
    -H "Authorization: Bearer $POSTBREEZE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "content": "Behind the scenes from this week'\''s shoot — slide for the video 👇",
      "mediaIds": ["med_img_1","med_img_2","med_video_1","med_img_3"],
      "scheduledFor": "2026-06-02T12:00:30Z",
      "platforms": [{
        "accountId": "soc_threads_…",
        "platformOptions": { "platform": "THREADS", "kind": "CAROUSEL" }
      }]
    }'
  ```
</CodeGroup>

## Multi-post threads (`threadParts`)

Threads on Threads. Pass `threadParts` on `platformOptions` and Postbreeze
publishes the root caption first, then chains each entry as a text-only
reply via `reply_to_id`. The first thread part becomes the second post in
the chain; the next part replies to that, and so on.

```ts theme={null}
await postbreeze.posts.create({
  content: "Quick thread on what we shipped this week 🧵",
  platforms: [{
    accountId: "soc_threads_…",
    platformOptions: {
      platform: "THREADS",
      threadParts: [
        "1/ The new compose flow",
        "2/ Per-platform thread editing",
        "3/ Connected previews",
      ],
    },
  }],
});
```

<CodeGroup>
  ```js Node.js theme={null}
  const post = await postbreeze.posts.create({
    content: "Quick thread on what we shipped this week 🧵",
    scheduledFor: new Date(Date.now() + 30_000).toISOString(),
    platforms: [{
      accountId: "soc_threads_…",
      platformOptions: {
        platform: "THREADS",
        threadParts: [
          "1/ The new compose flow — faster, calmer, fewer tabs",
          "2/ Per-platform thread editing — tune Threads without breaking IG",
          "3/ Connected previews — see exactly what ships before it ships",
        ],
      },
    }],
  });
  ```

  ```python Python theme={null}
  res = requests.post(
      f"{API}/posts",
      headers={"Authorization": f"Bearer {os.environ['POSTBREEZE_API_KEY']}"},
      json={
          "content": "Quick thread on what we shipped this week 🧵",
          "scheduledFor": (datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat(),
          "platforms": [{
              "accountId": "soc_threads_…",
              "platformOptions": {
                  "platform": "THREADS",
                  "threadParts": [
                      "1/ The new compose flow",
                      "2/ Per-platform thread editing",
                      "3/ Connected previews",
                  ],
              },
          }],
      },
  )
  res.raise_for_status()
  ```

  ```bash curl theme={null}
  curl -X POST https://api.postbreeze.ai/api/v1/posts \
    -H "Authorization: Bearer $POSTBREEZE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "content": "Quick thread on what we shipped this week 🧵",
      "scheduledFor": "2026-06-02T12:00:30Z",
      "platforms": [{
        "accountId": "soc_threads_…",
        "platformOptions": {
          "platform": "THREADS",
          "threadParts": [
            "1/ The new compose flow",
            "2/ Per-platform thread editing",
            "3/ Connected previews"
          ]
        }
      }]
    }'
  ```
</CodeGroup>

### Constraints

* **Each part ≤ 500 characters.** The root caption and every follow-up share the same Threads cap — there's no "See more" fold to lean on.
* **Up to 25 follow-ups.** `threadParts.length` is capped at 25; for longer chains, split into multiple posts.
* **Follow-ups are text-only.** Media (images, videos, carousels) stays on the **root** post. Thread parts cannot carry attachments.
* **`firstComment` replies to the root, not the last part.** If you set both `threadParts` and `firstComment`, the first comment is posted as a reply to the root post (alongside `threadParts[0]`), not chained at the end of the thread.
* **Thread follow-ups count toward the reply quota.** Threads enforces 1,000 replies / 24h per account; a 25-part thread burns 25 of those.

See [Platform settings → Threads](/concepts/platform-settings#threads) for the full `platformOptions` schema.

## Media requirements

### Images

| Property        | Requirement                                                   |
| --------------- | ------------------------------------------------------------- |
| Images per post | 1 (single) or up to 20 (carousel)                             |
| Formats         | JPEG, PNG, WebP                                               |
| Max file size   | 8 MB                                                          |
| Aspect ratio    | Any (Threads crops to 1:1 in Feed)                            |
| Min resolution  | 320 × 320                                                     |
| Alt text        | ✅ Per image (use `altText` on `mediaItems` or `mediaAltText`) |

### Videos

| Property                     | Requirement                    |
| ---------------------------- | ------------------------------ |
| Videos per single-video post | 1                              |
| Videos per carousel          | up to 20 (can mix with images) |
| Formats                      | MP4, MOV                       |
| Max file size                | 1 GB                           |
| Max duration                 | 5 minutes                      |
| Aspect ratio                 | 9:16 to 16:9                   |
| Codecs                       | H.264 + AAC                    |

See [Media uploads](/concepts/media-uploads) for upload strategies (URL ingest vs presigned upload).

## Platform-specific fields

| Field         | Type                                               | Required            | Description                                                                                 |
| ------------- | -------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------- |
| `platform`    | `"THREADS"`                                        | Yes                 | Discriminator.                                                                              |
| `kind`        | `"TEXT"` \| `"IMAGE"` \| `"VIDEO"` \| `"CAROUSEL"` | No (default `TEXT`) | Auto-derived in compose from attached media. Pass explicitly when calling the API directly. |
| `threadParts` | `string[]` (each ≤ 500, max 25)                    | No                  | Text-only follow-ups chained as replies to the root post. Media stays on root.              |

Threads has no other publish-time settings.

## First comment

Pass `firstComment` on the platform entry. Postbreeze posts it as a reply to the **root** post using the same `reply_to_id` flow Threads uses for thread replies.

```js theme={null}
platforms: [{
  accountId: "soc_threads_…",
  platformOptions: { platform: "THREADS", kind: "TEXT" },
  firstComment: "Link in bio for the full story",
}]
```

Limit: 500 characters (same as the main post). When combined with `threadParts`, the first comment still attaches to the root — not to the final thread part.

## Analytics

| Metric                          | Available |
| ------------------------------- | --------- |
| Views (time-series)             | ✅         |
| Profile views (time-series)     | ✅         |
| Likes (cumulative total)        | ✅         |
| Replies (cumulative total)      | ✅         |
| Reposts (cumulative total)      | ✅         |
| Quotes (cumulative total)       | ✅         |
| Followers (account, cumulative) | ✅         |

Refresh cadence: every **14 days**.

<Warning>
  Threads splits time-series from cumulative metrics — only `views` and `profile_views` come back as daily series. Likes, replies, reposts, quotes, and followers are surfaced as the current total on the analytics page's headline cards (not the daily chart).
</Warning>

## Common errors

| Error                          | Meaning                                                                     | Fix                                             |
| ------------------------------ | --------------------------------------------------------------------------- | ----------------------------------------------- |
| `THREADS_CAPTION_TOO_LONG`     | Caption or a `threadParts` entry > 500 chars                                | Trim the offending part.                        |
| `THREADS_CAROUSEL_SIZE`        | Carousel \< 2 or > 20 items                                                 | Stay between 2–20.                              |
| `THREADS_CONTAINER_TIMEOUT`    | Container polled past the budget                                            | Postbreeze retries on the next tick.            |
| `THREADS_VIDEO_TOO_LONG`       | Video > 5 minutes                                                           | Trim.                                           |
| `THREADS_FIRST_COMMENT_FAILED` | Reply post call failed                                                      | Use the retry button in the post-detail dialog. |
| `THREADS_TOKEN_EXPIRED`        | Long-lived token expired or revoked                                         | Reconnect.                                      |
| `THREADS_RATE_LIMITED`         | 250 posts / 24h or 1,000 replies / 24h (thread follow-ups count as replies) | Wait — daily limits reset on a rolling window.  |

## What you can't do

* ❌ Quote-post via API (Threads doesn't expose this surface)
* ❌ Reply target in compose (Postbreeze v1 only reply-to-incoming; cold-start reply is deferred)
* ❌ Attach media to `threadParts` follow-ups (text-only by design — media stays on the root)
* ❌ Schedule via Threads' native scheduler
* ❌ Edit a post after publish
* ❌ Polls
* ❌ Stories
* ❌ Webhook subscriptions (polling-only for now in Postbreeze)

## Full control: nested shape

The nested shape (`caption` + `targets` + `socialAccountId` + `scheduledAt`)
is the alternative request envelope. Functionally equivalent to the flat
shape — pick whichever maps cleaner to your call site.

```js Node.js theme={null}
const post = await postbreeze.posts.create({
  caption: "Quick thread on what we shipped this week 🧵",
  scheduledAt: new Date(Date.now() + 30_000).toISOString(),
  targets: [{
    socialAccountId: "soc_threads_…",
    platformOptions: {
      platform: "THREADS",
      threadParts: [
        "1/ The new compose flow",
        "2/ Per-platform thread editing",
        "3/ Connected previews",
      ],
    },
  }],
});
```
