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
| Limit | Threads value |
|---|---|
| Text | 500 characters maximum |
| Post types | Text, single image, single video, carousel, or a multi-post thread of up to 25 posts |
| Media per post | 1 item, or 2–10 items per request for a carousel (Threads allows up to 20) |
| Maximum image size | 8 MB |
| Maximum video size | 1 GB |
| Mixed image and video | Carousels only; a single-media post is one image or one video |
| PostZen presigned-upload image types | JPEG/JPG, PNG, WebP, GIF |
| PostZen presigned-upload video types | MP4, MPEG, QuickTime, AVI, WebM, M4V |
| Multi-post threads | 1–25 posts in settings.threadItems; each post has its own 500 characters and its own media |
| Scheduling | Supported |
| Replies | Read, 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
customContenton 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.threadItemson 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 minutesresponse = 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 minutescurl "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"
}
]
}'Carousel posts
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 postresponse = 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 postcurl -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
| Requirement | Value |
|---|---|
| Images per post | 1 in a single-media post; up to 10 per request in a carousel |
| Maximum file size | 8 MB |
| PostZen presigned-upload types | JPEG/JPG, PNG, WebP, GIF |
| Can be combined with video | In a carousel only |
Videos
| Requirement | Value |
|---|---|
| Videos per post | 1 in a single-media post; up to 10 per request in a carousel |
| Maximum file size | 1 GB |
| PostZen presigned-upload types | MP4, MPEG, QuickTime, AVI, WebM, M4V |
| Can be combined with an image | In a carousel only |
Platform settings
Add Threads settings to the target object inside platforms, as shown in the text post example.
| Key | Type | Notes |
|---|---|---|
replyControl | "everyone" | "accountsYouFollow" | "mentionedOnly" | Controls who can reply to the post. On a multi-post thread it applies to the root post. |
threadItems | Array<{ 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
| Error | Meaning | Fix |
|---|---|---|
| Text over 500 characters | The 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 characters | The limit applies to each item in threadItems separately. | Split the item into two, or shorten it. |
N of M thread posts were published | Threads 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 items | A 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 MB | The image exceeds the Threads limit and PostZen issues a warning. | Use an image no larger than 8 MB. |
| Carousel item still processing | A 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 GB | The video exceeds the Threads file-size limit. | Use a video no larger than 1 GB. |
402 while connecting | The Free plan's 2 included accounts are already in use. | Upgrade your plan, then start the connection again. |
| OAuth state expired | The connect URL's state is more than 10 minutes old. | Request a new Threads connect URL and complete OAuth within 10 minutes. |