threads_publish_carousel
Publish a carousel post on Threads with 2-20 images or videos, including captions and reply controls.
Instructions
Publish a carousel post on Threads with 2-20 images/videos.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| items | Yes | Array of media items | |
| text | No | Caption text | |
| reply_control | No | Who can reply | |
| topic_tag | No | Topic tag for the post | |
| quote_post_id | No | ID of a post to quote |
Implementation Reference
- src/tools/threads/publishing.ts:131-182 (handler)The handler for `threads_publish_carousel` which creates individual media containers and then publishes them as a carousel.
// ─── threads_publish_carousel ──────────────────────────────── server.tool( "threads_publish_carousel", "Publish a carousel post on Threads with 2-20 images/videos.", { items: z.array(z.object({ type: z.enum(["IMAGE", "VIDEO"]).describe("Media type"), url: z.string().url().describe("Public HTTPS URL"), alt_text: z.string().max(1000).optional().describe("Alt text for this item"), })).min(2).max(20).describe("Array of media items"), text: z.string().max(500).optional().describe("Caption text"), reply_control: z.enum(["everyone", "accounts_you_follow", "mentioned_only", "parent_post_author_only", "followers_only"]).optional().describe("Who can reply"), topic_tag: z.string().max(50).optional().describe("Topic tag for the post"), quote_post_id: z.string().optional().describe("ID of a post to quote"), }, async ({ items, text, reply_control, topic_tag, quote_post_id }) => { try { const childIds: string[] = []; for (const item of items) { const params: Record<string, unknown> = { media_type: item.type, is_carousel_item: true }; if (item.type === "IMAGE") { params.image_url = item.url; } else { params.video_url = item.url; } if (item.alt_text) params.alt_text = item.alt_text; const { data: child } = await client.threads("POST", `/${client.threadsUserId}/threads`, params); const childId = (child as { id: string }).id; if (item.type === "VIDEO") { await waitForThreadsContainer(client, childId); } childIds.push(childId); } const carouselParams: Record<string, unknown> = { media_type: "CAROUSEL", children: childIds.join(","), }; if (text) carouselParams.text = text; if (reply_control) carouselParams.reply_control = reply_control; if (topic_tag) carouselParams.topic_tag = topic_tag; if (quote_post_id) carouselParams.quote_post_id = quote_post_id; const { data: carousel } = await client.threads("POST", `/${client.threadsUserId}/threads`, carouselParams); const carouselId = (carousel as { id: string }).id; const { data, rateLimit } = await client.threads("POST", `/${client.threadsUserId}/threads_publish`, { creation_id: carouselId, }); return { content: [{ type: "text", text: JSON.stringify({ ...data as object, _rateLimit: rateLimit }, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Publish carousel failed: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } );