Skip to main content

Quick reference

FieldValue
Character limit500 characters (per post — applies to root and every thread part)
Carousel size2–20 items
Mixed media in carousel✅ (image + video)
Images per post1–20
Videos per post1 (single video post) or any subset of a carousel
Image formatsJPEG, PNG, WebP
Video formatsMP4, MOV
Max image size8 MB
Max video size1 GB
Max video duration5 minutes
Post typesText, 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 typesThreads 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.
SDK
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.

Media requirements

Images

PropertyRequirement
Images per post1 (single) or up to 20 (carousel)
FormatsJPEG, PNG, WebP
Max file size8 MB
Aspect ratioAny (Threads crops to 1:1 in Feed)
Min resolution320 × 320
Alt text✅ Per image (use altText on mediaItems or mediaAltText)

Videos

PropertyRequirement
Videos per single-video post1
Videos per carouselup to 20 (can mix with images)
FormatsMP4, MOV
Max file size1 GB
Max duration5 minutes
Aspect ratio9:16 to 16:9
CodecsH.264 + AAC
See Media uploads for upload strategies (URL ingest vs presigned upload).

Platform-specific fields

FieldTypeRequiredDescription
platform"THREADS"YesDiscriminator.
kind"TEXT" | "IMAGE" | "VIDEO" | "CAROUSEL"No (default TEXT)Auto-derived in compose from attached media. Pass explicitly when calling the API directly.
threadPartsstring[] (each ≤ 500, max 25)NoText-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.
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

MetricAvailable
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

ErrorMeaningFix
THREADS_CAPTION_TOO_LONGCaption or a threadParts entry > 500 charsTrim the offending part.
THREADS_CAROUSEL_SIZECarousel < 2 or > 20 itemsStay between 2–20.
THREADS_CONTAINER_TIMEOUTContainer polled past the budgetPostbreeze retries on the next tick.
THREADS_VIDEO_TOO_LONGVideo > 5 minutesTrim.
THREADS_FIRST_COMMENT_FAILEDReply post call failedUse the retry button in the post-detail dialog.
THREADS_TOKEN_EXPIREDLong-lived token expired or revokedReconnect.
THREADS_RATE_LIMITED250 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.
Node.js
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",
      ],
    },
  }],
});