Posts
Publish text, image, video, and multi-image on Threads with threads and quotes.
Publish to Threads through the shared posts surface. Text, images, video, carousels, reply chains, and quotes all go through the same endpoint.
Quick Start
Post to Threads in under 60 seconds:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"destinations": [
{
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "threads",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.threads.com/@you/post/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}The response is 202 Accepted with status: "queued". Poll GET /v1/posts/:id until the destination is published or failed, or receive webhooks instead of polling.
Content Types
mediaKind | Content | Media count | Notes |
|---|---|---|---|
text | Text-only post | No media | Max 500 characters |
image | Single image | Exactly 1 URL | Optional text |
multi-image | Carousel | 2 to 20 URLs | Images and videos can be mixed |
video | Single video | Exactly 1 URL | Optional text |
stories | No | No | Not supported on Threads |
Text is capped at 500 characters everywhere: posts, captions, and thread segments.
Image Post
A single image with an optional caption:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"destinations": [
{
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "threads",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.threads.com/@you/post/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Video Post
A single video with an optional caption. Max 1 GB, up to 5 minutes:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Watch how it works",
"mediaUrls": [
"https://cdn.onepostly.com/media/demo.mp4"
],
"mediaKind": "video",
"destinations": [
{
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Watch how it works",
"mediaUrls": [
"https://cdn.onepostly.com/media/demo.mp4"
],
"mediaKind": "video",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "threads",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.threads.com/@you/post/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Carousel
Up to 20 items in a single swipeable post. Images and videos can be mixed:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Product launch day! Here are all the new features:",
"mediaUrls": [
"https://cdn.example.com/feature1.jpg",
"https://cdn.example.com/feature2.jpg",
"https://cdn.example.com/feature3.jpg"
],
"mediaKind": "multi-image",
"destinations": [
{
"accountId": "YOUR_THREADS_ACCOUNT_ID"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Product launch day! Here are all the new features:",
"mediaUrls": [
"https://cdn.example.com/feature1.jpg",
"https://cdn.example.com/feature2.jpg",
"https://cdn.example.com/feature3.jpg"
],
"mediaKind": "multi-image",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "YOUR_THREADS_ACCOUNT_ID",
"platform": "threads",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.threads.com/@you/post/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Thread
Top-level text with normal mediaKind and mediaUrls rules is always the root post. threadItems adds 1 to 25 replies under that root in order.
| Property | Type | Description |
|---|---|---|
threadItems | array | Replies under the root post in order (1 to 25) |
threadItems[].content | string | Post text content |
threadItems[].mediaItems | array | Media attachments for this reply |
When threadItems is present the root needs top-level text or mediaUrls or the request fails with 400. The response post echoes the root only and never echoes threadItems.
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Here is everything you need to know about our API...",
"mediaUrls": [
"https://cdn.onepostly.com/media/cover.jpg"
],
"mediaKind": "image",
"destinations": [
{
"accountId": "YOUR_THREADS_ACCOUNT_ID"
}
],
"threadItems": [
{
"content": "1/ First, authentication is simple..."
},
{
"content": "2/ Next, create your first post...",
"mediaItems": [
"https://cdn.onepostly.com/media/step2a.jpg",
"https://cdn.onepostly.com/media/step2b.jpg"
]
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Here is everything you need to know about our API...",
"mediaUrls": [
"https://cdn.onepostly.com/media/cover.jpg"
],
"mediaKind": "image",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "YOUR_THREADS_ACCOUNT_ID",
"platform": "threads",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.threads.com/@you/post/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}- Each item's
contentmay be empty only when that item hasmediaItems - Every reply is a full Threads post with one image or video per item or several images as a carousel with a max of 20
- Each segment must stay within 500 characters
- All destinations in the request must be X, Threads, or Bluesky when
threadItemsis present
Quote
Pass quoteTweetId with the id of the Threads post being quoted:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "My take",
"mediaUrls": [],
"mediaKind": "text",
"destinations": [
{
"accountId": "YOUR_THREADS_ACCOUNT_ID",
"quoteTweetId": "THREADS_POST_ID"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "My take",
"mediaUrls": [],
"mediaKind": "text",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "YOUR_THREADS_ACCOUNT_ID",
"platform": "threads",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.threads.com/@you/post/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Destination Parameters
Prop
Type
Media Requirements
Images
| Property | Requirement |
|---|---|
| Max images | 20 per carousel (1 for a single image post) |
| Formats | JPEG, PNG |
| Max file size | 8 MB per image |
| Recommended | 1080 × 1350 px (4:5 portrait) |
Aspect ratios
| Ratio | Dimensions | Notes |
|---|---|---|
| 4:5 | 1080 × 1350 px | Portrait, recommended |
| 1:1 | 1080 × 1080 px | Square |
| 16:9 | 1080 × 608 px | Landscape |
Videos
| Property | Requirement |
|---|---|
| Max videos | 1 per post (single video or carousel slide) |
| Format | MP4 (H.264 / AAC) |
| Max file size | 1 GB |
| Max duration | 5 minutes |
| Aspect ratio | 9:16 vertical, 16:9 landscape, or 1:1 square |
| Resolution | 1080p recommended |
Media URL Requirements
These do not work as media URLs:
- Google Drive returns an HTML download page, not the file
- Dropbox returns an HTML preview page
- OneDrive / SharePoint returns HTML
- iCloud returns HTML
Test your URL in an incognito browser window. If you see a webpage instead of the raw image or video, the URL will not work.
Media URLs must be publicly accessible, return actual media bytes with the correct Content-Type, and use HTTPS.
Schedule
Add scheduledFor (local wall time) and timezone to the same call. The request shape is otherwise identical to an immediate post.
timezonedefaults toUTC. Use an IANA name likeEurope/Istanbul- Do not append
Zor an offset toscheduledFor. It is local wall time - Scheduled posts return
201 Createdand publish automatically at the scheduled moment - Max 1 year ahead
- Cancel with
posts.cancelPost({ id })/posts.cancel_post(id=…), orDELETE /v1/posts/:id, any time before then
Remote Delete
Removes the post and its replies, and emits the post.platform.deleted webhook:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.deletePostDestination({
destinationId: "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "threads",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.threads.com/@you/post/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Publish Errors
| Code | Cause | Fix |
|---|---|---|
TEXT_REQUIRED | Text-only post with empty message | Add text |
TEXT_TOO_LONG | Over 500 characters (post, segment, or firstComment) | Trim the text |
INVALID_TOPIC_TAG | Empty, over 50 characters, or contains . / & | Fix the tag |
MEDIA_REQUIRED | Image / video without a URL | Add at least one entry to mediaUrls |
UNSUPPORTED_MEDIA_KIND | mediaKind: "stories" on a Threads destination | Use text, image, multi-image, or video |
INVALID_MULTI_IMAGE | Fewer than 2 or more than 20 URLs | Adjust the count |
MEDIA_UNFETCHABLE | Threads could not download the URL | Use a public HTTPS URL |
CONTAINER_FAILED | Media processing failed on Threads' side | Retry. Check the file opens correctly |
TOKEN_INVALID | Connection token expired or revoked | Reconnect the account |
PERMISSION_DENIED | Connection lacks publish permission | Reconnect the account |
RATE_LIMITED | Threads throttled the request | Retry later |