Threads
Connect a Threads account and publish text, single-image, and single-video posts with the PostZen API.
PostZen publishes and schedules text, single-image, and single-video posts to Threads — each Threads target can also control who is allowed to reply.
Quick reference
| Limit | Threads value |
|---|---|
| Text | 500 characters maximum |
| Post types | Text, single image, or single video |
| Images per post | 1 |
| Maximum image size | 8 MB |
| Videos per post | 1 |
| Maximum video size | 1 GB |
| Mixed image and video | Not supported |
| PostZen presigned-upload image types | JPEG/JPG, PNG, WebP, GIF |
| PostZen presigned-upload video types | MP4, MPEG, QuickTime, AVI, WebM, M4V |
| Scheduling | Supported |
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.
Threads currently accepts one image per post through PostZen. Multi-image carousels are coming soon — they are not yet supported.
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 tier allows 2 connected accounts before a payment method is required. Connecting another account after that 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 either one image or one video. You cannot attach multiple images or mix an image and a video.
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. Threads supports one image only through PostZen; adding another image is not yet supported.
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"
}
]
}'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 |
| Maximum file size | 8 MB |
| PostZen presigned-upload types | JPEG/JPG, PNG, WebP, GIF |
| Can be combined with video | No |
Videos
| Requirement | Value |
|---|---|
| Videos per post | 1 |
| Maximum file size | 1 GB |
| PostZen presigned-upload types | MP4, MPEG, QuickTime, AVI, WebM, M4V |
| Can be combined with an image | No |
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. |
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 multi-image carousels. Carousels are coming soon; PostZen currently accepts one image per Threads post.
- Publish multi-post thread chains. PostZen does not yet support them.
- Create polls or quote posts, or use GIF search.
- Mix images and video in one post.
- Edit a published post.
- Read or manage DMs, inbox messages, comments, or replies.
- 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. |
| More than one image | PostZen currently supports one image per Threads post. | Keep one image. Multi-image carousels are coming soon. |
| Image over 8 MB | The image exceeds the Threads limit and PostZen issues a warning. | Use an image no larger than 8 MB. |
| Image and video in one post | Threads posts through PostZen cannot mix media types. | Keep either one image or one video. |
| Video over 1 GB | The video exceeds the Threads file-size limit. | Use a video no larger than 1 GB. |
402 while connecting | The profile is over the free-tier allowance of 2 connected accounts without a payment method. | Add a payment method, 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. |