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
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.
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.
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 for the full options reference and 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.
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" },
}],
});
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.
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.
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" },
}],
});
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.
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",
],
},
}],
});
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",
],
},
}],
});
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 for the full platformOptions schema.
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 for upload strategies (URL ingest vs presigned upload).
| 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.
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.
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.
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).
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.
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",
],
},
}],
});