Instagram carousel API
Publish Instagram carousels through an API or MCP server: the 10-item limit, the 4:5 to 1.91:1 aspect rule, example requests, daily limits, and every error with its fix.
Early access: publishing carousels to Instagram is built and being tested in the REST API, @turboyap/sdk and the @turboyap/mcp server. It opens to early-access users once Turboyap passes Meta App Review. Meanwhile, test mode runs the same requests against a simulated Instagram account.
What an Instagram carousel is
An Instagram carousel is one feed post with up to 10 images that viewers swipe through. The Instagram API builds it from one media container per image plus a carousel container, then publishes the carousel. In Turboyap it's type: "slideshow" sent to an Instagram account.
Turboyap creates the containers, converts every slide to JPEG, keeps all slides at one aspect ratio (Instagram crops every slide to the first one's ratio), waits for Instagram to finish processing, and publishes.
Limits
What Instagram accepts for this post type. Turboyap's planner checks every one of these before a post is scheduled (the same data as platform rules), and each value links to the Instagram documentation it comes from.
| Limit | Value | Source |
|---|---|---|
| Images per post | 2–10 | Meta: IG User media reference |
| Formats | JPEG (Turboyap converts anything else to JPEG) | Meta: IG User media reference |
| File size | 8 MB per image | Meta: IG User media reference |
| Dimensions | 320–1440px wide (Turboyap resizes to fit) | Meta: IG User media reference |
| Aspect ratio | 4:5 to 1.91:1 | Meta: IG User media reference |
| Caption | 2,200 characters (sent as caption) | Meta: IG User media reference |
| Hashtags | 30 per caption | Meta: IG User media reference |
| @mentions | 20 per caption | Meta: IG User media reference |
| Rate limit | 100 API-published posts per account per 24 hours (a carousel counts as one) | Meta: content publishing |
Compare with other platforms in the social media API limits tracker.
Example requests
All three do the same thing: upload the media, preview the post with a dry run, then schedule it. The idempotency key makes retries safe: send the same request again with the same key and you get the original post back, not a second one.
Early access: api.turboyap.com isn't public yet, and @turboyap/sdk and @turboyap/mcp aren't on npm yet. These examples show the API as built.
curl
# 1. Upload each image. The body is the raw file; Content-Type is its type.
curl https://api.turboyap.com/v1/media \
-H "Authorization: Bearer $TURBOYAP_API_KEY" \
-H "Content-Type: image/png" \
-H "X-Filename: slide-1.png" \
--data-binary @slide-1.png
# → { "media": { "id": "med_…", "width": 1080, "height": 1920, … }, "deduplicated": false }
# 2. Preview. Remove "dry_run" to create and schedule the post.
curl https://api.turboyap.com/v1/posts \
-H "Authorization: Bearer $TURBOYAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "slideshow",
"media_ids": ["med_…", "med_…", "med_…"],
"caption": "Five label tricks brands hope you miss. Save this for your next grocery run.",
"targets": [{ "account_id": "acct_instagram…" }],
"schedule_at": "2026-10-20T18:00:00-04:00",
"fit": { "overflow": "truncate", "aspect": "pad" },
"idempotency_key": "instagram-carousel-2026-10-20",
"dry_run": true
}' TypeScript with @turboyap/sdk
import { readFile } from "node:fs/promises";
import { TurboyapClient } from "@turboyap/sdk";
const ty = new TurboyapClient({ apiKey: process.env.TURBOYAP_API_KEY! });
const account = (await ty.listAccounts()).find((a) => a.platform === "instagram")!;
const media_ids: string[] = [];
for (const file of ["slide-1.png", "slide-2.png", "slide-3.png"]) {
const { media } = await ty.uploadMedia(await readFile(file), { filename: file, contentType: "image/png" });
media_ids.push(media.id);
}
const post = {
type: "slideshow" as const,
media_ids,
caption: "Five label tricks brands hope you miss. Save this for your next grocery run.",
targets: [{ account_id: account.id }],
schedule_at: "2026-10-20T18:00:00-04:00",
fit: { overflow: "truncate" as const, aspect: "pad" as const },
idempotency_key: "instagram-carousel-2026-10-20",
};
// Preview first: every problem comes back with a fix.
const preview = await ty.createPost({ ...post, dry_run: true });
if (!preview.ok) throw new Error(preview.errors.map((e) => `${e.code}: ${e.fix}`).join("\n"));
const { post: created } = await ty.createPost(post);
console.log(created.targets[0]?.status, created.targets[0]?.next_step); Claude Code (MCP)
Add the Turboyap MCP server once, with an agent key from your workspace:
claude mcp add turboyap -e TURBOYAP_API_KEY=ty_agent_… -- npx -y @turboyap/mcp Then ask:
Upload the slides in ~/content/labels and schedule them as an Instagram carousel for Thursday noon ET. They're 9:16, so pad them to fit Instagram without cutting text. Dry-run first.
The agent calls list_accounts, get_platform_rules, upload_media, create_post with dry_run: true, applies any fixes, calls create_post again to schedule, and reports each account's next_step from get_post. The same server works in Cursor; see the MCP quickstart.
How publishing works
Instagram has one publishing mode. When the scheduled time arrives, Turboyap creates the media containers and the carousel container, waits for Instagram to finish processing, then publishes. The target moves scheduled → publishing → processing → published, and platform_url links to the live post.
JPEG only; Turboyap converts PNG/WebP automatically.
Instagram crops every carousel image to the first image's aspect ratio, so Turboyap normalizes all slides to one ratio.
Vertical 9:16 slides are outside Instagram's 4:5–1.91:1 range: set fit.aspect to "pad" (keeps all content) or "crop".
Common errors and fixes
Every error has a code, the field that caused it, and a fix an agent can apply without asking you. A request with several problems returns all of them in issues; a dry run returns them without creating anything.
| Code | When | Fix |
|---|---|---|
too_many_items | More than 10 images for Instagram. | Set fit.overflow to "truncate" to post the first 10, or pass targets[i].media_ids with your best 10 slides (keep your call-to-action slide). |
too_few_items | Fewer than 2 images. One slide posts as a single image instead, with a warning. | Add media so there are at least 2, or send it as an image post. |
aspect_ratio_out_of_range | An image is outside 4:5 to 1.91:1 and fit.aspect is "strict" (the default). | Set fit.aspect to "pad" to letterbox (keeps all text visible) or "crop" to center-crop. |
caption_too_long | Caption over 2,200 characters. | Shorten it, or pass targets[i].caption with a version under 2,200 characters. |
too_many_hashtags | More than 30 hashtags. | Keep at most 30 hashtags. |
too_many_mentions | More than 20 @mentions. | Keep at most 20 mentions. |
media_format_unsupported | A media id points to a video, or the upload isn't an image Turboyap can read. | Upload PNG, JPEG or WebP images. Video posts are coming soon. |
schedule_in_past | schedule_at is more than a minute in the past. | Use "now" or a future time with a timezone offset. |
account_unavailable | The Instagram account needs to be reconnected. | Reconnect the account, then retry. |
unsupported_platform | A target is on a platform Turboyap doesn't support yet. | Remove that target. Supported platforms: tiktok, instagram, bluesky (beta), telegram (beta), discord (beta). |
insufficient_scope | The key lacks a scope the request needs, such as publish for schedule_at: "now" (agent keys don't have it by default). | Schedule a future time instead of "now", omit schedule_at to save a draft, or use a key with the missing scope. GET /v1/keys/me lists the key's scopes. |
account_not_allowed | The key is limited to some accounts, and a target isn't one of them. | Remove that target, or use a key whose account_ids include the account. |
forbidden_scope | An agent key tries to approve or reject a post. | A human approves agent posts. To skip review for an account, a human sets its autonomy level to 3. |
unknown_outcome | Turboyap stopped partway through publishing (for example, a restart) and can't confirm whether the post went out. It doesn't publish it again. | Check the account on the platform. If the post isn't there, create it again. |
Errors from Instagram itself appear on the target's error with Instagram's own code. Turboyap retries rate limits and temporary failures with backoff, and marks the account for reconnection when Instagram rejects its token.
Approval and audit
- Level 1, review everything (default): a post an agent schedules starts as
pending_approvaland waits until a human approves it with a human key (POST /v1/posts/:id/approve). Agent keys can't approve. - Level 3, full autopilot: agent posts schedule directly. Every action is still logged. Level 2, guardrails, is coming soon.
- Posts created by humans schedule directly at any level.
- Edits re-open review: if an agent changes the caption or time of an approved post on a level-1 account, it goes back to
pending_approval. - Dry runs show the outcome: each target's
status_on_createtells the agent whether the post will wait for approval. - Audit log: every create, approval, edit, publish and failure is recorded with whether a human or an agent did it, the key id, and the MCP client name. The kill switch (
POST /v1/workspace/pause) stops every publish to Instagram and every other platform at once.
Read more in the autonomy dial.
FAQ
How many images can an Instagram carousel have through the API?
Between 2 and 10. Send more and Turboyap returns too_many_items; set fit.overflow to "truncate" to post the first 10, or choose the 10 yourself with a per-target media_ids.
Why does Instagram crop my carousel slides?
Instagram crops every carousel item to the first item's aspect ratio. Turboyap normalizes all slides to one ratio inside Instagram's 4:5 to 1.91:1 range, padding with fit.aspect: "pad" or cropping with "crop".
Can I post 9:16 vertical slides to an Instagram carousel?
Not as-is: 9:16 is taller than Instagram's 4:5 limit. Set fit.aspect to "pad" to letterbox each slide to 4:5 without losing any text, or "crop" to center-crop.
How many carousels can I publish per day through the Instagram API?
Meta allows 100 API-published posts per account in a rolling 24 hours, and a carousel counts as one post. The same Meta page also mentions 50; see the limits tracker.
Related
- Instagram image post API
- TikTok slideshow API
- Bluesky multi-image post API (beta)
- Telegram album API (beta)
- Discord multi-image message API (beta)
- Post one slideshow to TikTok and Instagram
- REST API reference
- Platform rules
- Social media API limits tracker
This page as markdown: /api/instagram/carousel.md. Everything for LLMs: /llms.txt.