Blog

Instagram carousels through the API: the limits nobody documents together

· 3 min read · CyberFreezeDev

If you have ever built against the Instagram Graph API, you already know the documentation is spread across four pages that never quite agree. Carousels are the worst of it: the limits, the container lifecycle and the failure modes are each documented somewhere, and never in the same place.

Here is the whole thing in one page.

There is no endpoint that takes an array of images. Publishing a carousel is:

  1. One container per slide. POST /{ig-user-id}/media with image_url (or video_url) and, critically, is_carousel_item=true.
  2. One parent container. POST /{ig-user-id}/media with media_type=CAROUSEL, the caption, and children set to the comma-separated list of child container ids.
  3. Publish. POST /{ig-user-id}/media_publish with the parent's id.

Miss is_carousel_item=true on a child and Meta happily creates a normal single-image container. You will not find out until step two rejects it.

Ten slides, not twenty

The app lets someone swipe together twenty images. The API stops at ten.

This catches people out because it is a product limit expressed as an API error, and the error does not say "the app allows more". If your source of truth is what you can do on your phone, you will build a feature that fails for your users on the eleventh slide.

There is no flag, permission or review process that raises it.

Containers are asynchronous, and that is the real work

The call that creates a container returns an id immediately. The container is not ready.

GET /{container-id}?fields=status_code

status_code moves through IN_PROGRESS to FINISHED, or to ERROR. You cannot publish until every child and the parent report FINISHED. Publishing early returns an error about media not being ready, which reads like a bug in your code and is actually a race you have to wait out.

For images this is usually a second or two. For video it can be minutes, and a carousel mixing both is as slow as its slowest video.

Polling has to have a budget

This is the part that turns a working integration into a broken one at 3am.

If you poll forever, a stuck container holds a worker open. If your scheduler also has a "this job looks stuck, reclaim it" timeout, and your polling budget is longer than that timeout, a second worker picks up the same post while the first is still waiting. Both eventually publish. The customer gets the same carousel twice, on their real audience, and there is no undo.

The fix is arithmetic, not cleverness: your total polling budget must be comfortably less than your stuck-job timeout. Half is a reasonable choice. If your scheduler reclaims after five minutes, stop polling at two and a half and fail the job cleanly.

What to do when a slide fails

A child container that reaches ERROR cannot be repaired. The parent cannot be created without it, and the ids are not reusable.

The honest behaviour is to fail the whole post with a message naming the slide, rather than publishing a carousel with a hole in it. A four-slide carousel posted as three slides is not a partial success; it is a different post, and usually one the author would not have approved.

The short version

Build the children, wait for all of them, build the parent, wait for it, publish. Cap at ten. Budget your polling against your own reclaim timeout, not against Meta's patience.

Everything else about Instagram publishing is easier than this.

Frequently asked questions

How many images can an Instagram carousel have via the API?

Ten. The Instagram mobile app allows twenty, but the Graph API rejects a carousel with more than ten children, and there is no permission or flag that raises it.

Why does my Instagram carousel publish fail with a media not ready error?

Container creation is asynchronous. Each child container has to reach status_code FINISHED before the parent can be published, so you must poll the status field and wait rather than publishing immediately.

Can an Instagram carousel mix images and videos through the API?

Yes. Both are created as children with is_carousel_item=true, but video containers take much longer to reach FINISHED, so the whole carousel waits on the slowest one.