PostZen

Threads

Connect a Threads account and publish text, image, video, carousel, and multi-post thread posts with the PostZen API.

PostZen publishes and schedules text, image, video, and carousel posts to Threads — each Threads target can also control who is allowed to reply. Attach one item for a single-media post, or 2–10 items in one request for a carousel. To publish a chain of connected posts, put every post in settings.threadItems.

Quick reference

LimitThreads value
Text500 characters maximum
Post typesText, single image, single video, carousel, or a multi-post thread of up to 25 posts
Media per post1 item, or 2–10 items per request for a carousel (Threads allows up to 20)
Maximum image size8 MB
Maximum video size1 GB
Mixed image and videoCarousels only; a single-media post is one image or one video
PostZen presigned-upload image typesJPEG/JPG, PNG, WebP, GIF
PostZen presigned-upload video typesMP4, MPEG, QuickTime, AVI, WebM, M4V
Multi-post threads1–25 posts in settings.threadItems; each post has its own 500 characters and its own media
SchedulingSupported
RepliesRead, reply, and hide through the inbox; delete is not available

Threads text is limited to 500 characters. This is the most common Threads cross-posting failure. Use customContent on the Threads target when the shared content is longer.

Attach 2 or more mediaItems and PostZen publishes a carousel: images and videos can be mixed, and the items appear in the order you send them. Threads counts a carousel as one post against its 250-post daily limit.

Before you start

  • Your Threads profile must be tied to an Instagram account. Sign in with the Instagram account that has Threads enabled when you complete OAuth.
  • You need the PostZen profile ID that will own the connected Threads account.
  • The Free plan includes 2 connected accounts and does not allow extras. Connecting a third account returns a 402.
  • Keep Threads content at or below 500 characters. For a cross-platform post, set customContent on the Threads target to provide a shorter version.
  • Attach one image or one video for a single-media post, or 2–10 images and videos for a carousel. Mixing an image and a video is only possible in a carousel.
  • To publish a multi-post thread, put every post in settings.threadItems on the Threads target. The 500-character limit and the media rules apply to each item separately.

Media URLs must be publicly accessible direct links to the file. Google Drive, Dropbox, OneDrive, and iCloud share links return HTML pages instead of the media file. You can upload media through POST /v1/media/presign and use the returned publicUrl instead.

Connect your account

Threads uses OAuth 2.0. Request a connect URL for your PostZen profile, then send the user to the returned authUrl. PostZen manages the OAuth scopes internally. The returned state expires after 10 minutes.

const { data } = await postzen.connect.createConnectUrl({
  path: { platform: 'threads' },
  query: {
    profileId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
    redirectUrl: 'https://yourapp.com/connected',
  },
});

console.log(data.authUrl); // redirect the user here
console.log(data.state);   // expires after 10 minutes
response = client.connect.create_connect_url(
    "threads",
    profile_id="jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
    redirect_url="https://yourapp.com/connected",
)

print(response.authUrl)  # redirect the user here
print(response.state)    # expires after 10 minutes
curl "https://api.postzen.dev/v1/connect/threads?profileId=jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e&redirectUrl=https://yourapp.com/connected" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

The response is { authUrl, state }. Complete the OAuth flow at authUrl with the Instagram account that has Threads enabled.

Quick start

Publish a text post immediately by targeting the connected account with the threads platform value.

const { data } = await postzen.posts.createPost({
  body: {
    content: 'Hello from Threads!',
    publishNow: true,
    platforms: [
      {
        platform: 'threads',
        accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
      },
    ],
  },
});

console.log(data.post._id);
response = client.posts.create_post(
    content="Hello from Threads!",
    publish_now=True,
    platforms=[
        {
            "platform": "threads",
            "account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
        },
    ],
)

print(response.post.field_id)
curl -X POST https://api.postzen.dev/v1/posts \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello from Threads!",
    "publishNow": true,
    "platforms": [
      {
        "platform": "threads",
        "accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e"
      }
    ]
  }'

Set exactly one of publishNow, scheduledFor, or isDraft when you create a post. To schedule it, replace publishNow with an ISO-8601 scheduledFor value at least 60 seconds in the future.

Content types

Text posts

Threads text posts accept up to 500 characters. Set replyControl in the Threads target's settings object to choose who can reply.

const { data } = await postzen.posts.createPost({
  body: {
    content: 'What are you building this week?',
    publishNow: true,
    platforms: [
      {
        platform: 'threads',
        accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
        settings: {
          replyControl: 'accountsYouFollow',
        },
      },
    ],
  },
});

console.log(data.post._id);
response = client.posts.create_post(
    content="What are you building this week?",
    publish_now=True,
    platforms=[
        {
            "platform": "threads",
            "account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
            "settings": {
                "reply_control": "accountsYouFollow",
            },
        },
    ],
)

print(response.post.field_id)
curl -X POST https://api.postzen.dev/v1/posts \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "What are you building this week?",
    "publishNow": true,
    "platforms": [
      {
        "platform": "threads",
        "accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
        "settings": {
          "replyControl": "accountsYouFollow"
        }
      }
    ]
  }'

For a cross-platform post whose shared content exceeds 500 characters, add a shorter customContent value to the Threads target.

Single-image posts

Add one image in mediaItems. To publish more than one image, send 2–10 items and PostZen creates a carousel.

const { data } = await postzen.posts.createPost({
  body: {
    content: 'A first look at the new design.',
    publishNow: true,
    mediaItems: [
      { url: 'https://cdn.example.com/design.jpg' },
    ],
    platforms: [
      {
        platform: 'threads',
        accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
      },
    ],
  },
});

console.log(data.post._id);
response = client.posts.create_post(
    content="A first look at the new design.",
    publish_now=True,
    media_items=[
        {"url": "https://cdn.example.com/design.jpg"},
    ],
    platforms=[
        {
            "platform": "threads",
            "account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
        },
    ],
)

print(response.post.field_id)
curl -X POST https://api.postzen.dev/v1/posts \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "A first look at the new design.",
    "publishNow": true,
    "mediaItems": [
      { "url": "https://cdn.example.com/design.jpg" }
    ],
    "platforms": [
      {
        "platform": "threads",
        "accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e"
      }
    ]
  }'

The image must be 8 MB or smaller. PostZen warns when an image exceeds this limit.

Single-video posts

Add one video in mediaItems. A Threads video must be 1 GB or smaller, and the post cannot also contain an image.

const { data } = await postzen.posts.createPost({
  body: {
    content: 'Watch the new workflow in action.',
    publishNow: true,
    mediaItems: [
      { url: 'https://cdn.example.com/workflow.mp4' },
    ],
    platforms: [
      {
        platform: 'threads',
        accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
      },
    ],
  },
});

console.log(data.post._id);
response = client.posts.create_post(
    content="Watch the new workflow in action.",
    publish_now=True,
    media_items=[
        {"url": "https://cdn.example.com/workflow.mp4"},
    ],
    platforms=[
        {
            "platform": "threads",
            "account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
        },
    ],
)

print(response.post.field_id)
curl -X POST https://api.postzen.dev/v1/posts \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Watch the new workflow in action.",
    "publishNow": true,
    "mediaItems": [
      { "url": "https://cdn.example.com/workflow.mp4" }
    ],
    "platforms": [
      {
        "platform": "threads",
        "accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e"
      }
    ]
  }'

Attach 2–10 items in mediaItems and PostZen publishes a Threads carousel. Images and videos can be mixed, and the items appear in the order you send them. No setting is needed — a Threads target with two or more media items is always a carousel. PostZen creates a child media container for each item, waits for every child to finish processing, creates the parent carousel container with your text and reply control, and publishes it, using the same resumable container handling as single-media posts.

const { data } = await postzen.posts.createPost({
  body: {
    content: 'Three angles on the new release.',
    publishNow: true,
    mediaItems: [
      { url: 'https://cdn.example.com/threads/release-1.jpg' },
      { url: 'https://cdn.example.com/threads/release-2.mp4' },
      { url: 'https://cdn.example.com/threads/release-3.jpg' },
    ],
    platforms: [
      {
        platform: 'threads',
        accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
        settings: {
          replyControl: 'accountsYouFollow',
        },
      },
    ],
  },
});

console.log(data.post._id);
response = client.posts.create_post(
    content="Three angles on the new release.",
    publish_now=True,
    media_items=[
        {"url": "https://cdn.example.com/threads/release-1.jpg"},
        {"url": "https://cdn.example.com/threads/release-2.mp4"},
        {"url": "https://cdn.example.com/threads/release-3.jpg"},
    ],
    platforms=[
        {
            "platform": "threads",
            "account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
            "settings": {
                "reply_control": "accountsYouFollow",
            },
        },
    ],
)

print(response.post.field_id)
curl -X POST https://api.postzen.dev/v1/posts \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Three angles on the new release.",
    "publishNow": true,
    "mediaItems": [
      { "url": "https://cdn.example.com/threads/release-1.jpg" },
      { "url": "https://cdn.example.com/threads/release-2.mp4" },
      { "url": "https://cdn.example.com/threads/release-3.jpg" }
    ],
    "platforms": [
      {
        "platform": "threads",
        "accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
        "settings": {
          "replyControl": "accountsYouFollow"
        }
      }
    ]
  }'

Each image follows the image limits and each video follows the video limits below. Threads allows up to 20 items in a carousel; a single PostZen request accepts up to 10 mediaItems. Alt text set on a media item is passed to Threads for that carousel item.

Multi-post threads

Put the whole thread in the Threads target's settings.threadItems, in order. The first item is the root post, and PostZen publishes every later item as a reply to the one before it, so the posts appear as one connected chain on your profile. Each item takes its own content (500 characters or fewer) and, optionally, its own mediaItems: one image, one video, or 2–10 images and videos for a carousel.

When threadItems is set, the target's content (or customContent) and the request's top-level mediaItems are not published to Threads. They still apply to the post's other platform targets, so a shared content can go to LinkedIn while Threads gets the chain. replyControl applies to the root post. The platformPostUrl in the response is the root post's URL.

const { data } = await postzen.posts.createPost({
  body: {
    content: 'A thread about API design',
    publishNow: true,
    platforms: [
      {
        platform: 'threads',
        accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
        settings: {
          threadItems: [
            {
              content: '1/ A thread about API design',
              mediaItems: [{ url: 'https://cdn.example.com/api-design.png' }],
            },
            { content: '2/ Use the HTTP method that matches the action.' },
            { content: '3/ Version the API from day one.' },
            { content: '4/ Document everything. /end' },
          ],
        },
      },
    ],
  },
});

console.log(data.post.platforms[0].platformPostUrl); // root post
response = client.posts.create_post(
    content="A thread about API design",
    publish_now=True,
    platforms=[
        {
            "platform": "threads",
            "account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
            "settings": {
                "thread_items": [
                    {
                        "content": "1/ A thread about API design",
                        "media_items": [{"url": "https://cdn.example.com/api-design.png"}],
                    },
                    {"content": "2/ Use the HTTP method that matches the action."},
                    {"content": "3/ Version the API from day one."},
                    {"content": "4/ Document everything. /end"},
                ],
            },
        },
    ],
)

print(response.post.platforms[0].platform_post_url)  # root post
curl -X POST https://api.postzen.dev/v1/posts \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "A thread about API design",
    "publishNow": true,
    "platforms": [
      {
        "platform": "threads",
        "accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
        "settings": {
          "threadItems": [
            {
              "content": "1/ A thread about API design",
              "mediaItems": [{ "url": "https://cdn.example.com/api-design.png" }]
            },
            { "content": "2/ Use the HTTP method that matches the action." },
            { "content": "3/ Version the API from day one." },
            { "content": "4/ Document everything. /end" }
          ]
        }
      }
    ]
  }'

Every item is validated before anything is published, so a thread with one item over 500 characters is rejected with a 400 and nothing goes out. The create response and GET /v1/posts/{postId} echo the thread back under platforms[].settings.threadItems, with each item's media as hosted mediaItems[].url values, so an agent can review a draft thread before publishing it. If Threads rejects an item part-way through, the target fails with a message that says how many posts were published; the published part stays on Threads, and retrying the target continues from the next item instead of reposting the root.

Threads counts the root post against its 250-post daily limit and every later post against a separate limit of 1,000 replies per day.

In the dashboard composer, turn on Thread under the Threads options to add follow-up posts, give each one its own media, and drag them into order.

Media requirements

You can pass an external URL or upload media through /v1/media/presign. PostZen downloads and re-hosts external URLs up to 100 MB. Presigned uploads support files up to 5 GB, but the stricter Threads limits below still apply.

Images

RequirementValue
Images per post1 in a single-media post; up to 10 per request in a carousel
Maximum file size8 MB
PostZen presigned-upload typesJPEG/JPG, PNG, WebP, GIF
Can be combined with videoIn a carousel only

Videos

RequirementValue
Videos per post1 in a single-media post; up to 10 per request in a carousel
Maximum file size1 GB
PostZen presigned-upload typesMP4, MPEG, QuickTime, AVI, WebM, M4V
Can be combined with an imageIn a carousel only

Platform settings

Add Threads settings to the target object inside platforms, as shown in the text post example.

KeyTypeNotes
replyControl"everyone" | "accountsYouFollow" | "mentionedOnly"Controls who can reply to the post. On a multi-post thread it applies to the root post.
threadItemsArray<{ content, mediaItems? }>Publishes a chain of 1–25 connected posts. See Multi-post threads.

PostZen analytics is live for Threads, including post metrics, follower counts, and best-time-to-post suggestions. Reposts of other people's threads report no metrics of their own.

What you can't do

  • Publish carousels with more than 10 items in one request. Threads allows up to 20.
  • Publish a thread as a reply to someone else's post, or continue a thread from a post that is already published.
  • Create polls or quote posts, or use GIF search.
  • Mix an image and a video outside a carousel.
  • Edit a published post.
  • Delete a reply, or hide a nested one. Threads only lets PostZen hide top-level replies on your own posts, and hiding one also hides the replies beneath it. See the inbox guide.
  • Read or send direct messages. Threads has no messaging API.
  • Create ads or receive engagement webhooks.

Common errors

ErrorMeaningFix
Text over 500 charactersThe Threads target exceeds its text limit. This is the most common cross-posting failure.Shorten content, or set a Threads-specific customContent value of 500 characters or fewer.
Thread item exceeds 500 charactersThe limit applies to each item in threadItems separately.Split the item into two, or shorten it.
N of M thread posts were publishedThreads rejected an item after earlier ones went live. The published part stays on Threads.Fix the failing item and retry the target; PostZen continues from the next unpublished item.
More than 10 media itemsA single request accepts up to 10 mediaItems.Send 2–10 items; Threads itself allows up to 20 per carousel.
Threads carousel items must be images or videos.A carousel item is not an image or a video.Attach only images and videos in mediaItems.
Image over 8 MBThe image exceeds the Threads limit and PostZen issues a warning.Use an image no larger than 8 MB.
Carousel item still processingA child container did not finish in time. PostZen retries the whole carousel automatically; nothing is published until the parent container is published, so a retry cannot duplicate the post.Wait for the retry. Very large videos take longer to process.
Video over 1 GBThe video exceeds the Threads file-size limit.Use a video no larger than 1 GB.
402 while connectingThe Free plan's 2 included accounts are already in use.Upgrade your plan, then start the connection again.
OAuth state expiredThe connect URL's state is more than 10 minutes old.Request a new Threads connect URL and complete OAuth within 10 minutes.

On this page