Skip to main content
Socialift
Technical guide

Instagram Content Publishing API: posting images, Reels and carousels

Publishing through the Instagram API takes two calls: POST /<IG_ID>/media creates a media container, and POST /<IG_ID>/media_publish publishes it. Meta downloads the media from a public URL at that moment, and images must be JPEG. Each account can publish 100 posts through the API in a rolling 24 hours; a carousel counts as one.

Published: Updated:

How does publishing through the Instagram API work?

In the standard flow you don't send the file in the request. You create a container that points to the media on a public URL, and Meta fetches and processes it. When the container's status_code is FINISHED you publish it with media_publish and receive the Instagram media ID. Videos take time to process, so their status has to be polled. Large videos can also use a resumable upload session.

The flow is the same in both setups. Instagram Login calls graph.instagram.com with an Instagram User token; Facebook Login calls graph.facebook.com with a Page access token and the Instagram account ID read from the Page.

Only professional accounts can publish. Shopping tags and filters are not supported through the API.

Permissions and tokens for publishing

Permissions and tokens for publishing — Capability matrix by platform
Instagram LoginFacebook Login
Hostgraph.instagram.comgraph.facebook.com
TokenInstagram User access tokenPage access token
Permissionsinstagram_business_basic, instagram_business_content_publishinstagram_basic, instagram_content_publish, pages_read_engagement
If the Page role comes via Business ManagernoAlso ads_management, ads_read
Access levelStandard for your own accounts; Advanced (App Review) for othersStandard for your own accounts; Advanced (App Review) for others

yesno

Supported media types and requirements

Supported media types and requirements — Capability matrix by platform
TypeHow to create the containerKey requirements
Imageimage_urlJPEG only (MPO and JPS not supported), up to 8 MB, aspect ratio between 4:5 and 1.91:1
Reelmedia_type=REELS + video_url3 seconds to 15 minutes, up to 300 MB, H.264 or HEVC, 23–60 fps; share_to_feed controls Feed visibility
CarouselChild containers with is_carousel_item=true, then media_type=CAROUSEL + childrenUp to 10 images, videos or a mix; counts as one post against the limit
Storymedia_type=STORIES + image or video URLDisappears after 24 hours; link, poll and location stickers aren't supported; business accounts only per Meta's Facebook Login docs
Notes and limits

Captions: up to 2,200 characters, 30 hashtags and 20 @-mentions. Image posts accept alt_text (since March 24, 2025; not for Reels or stories), and since June 22, 2026 is_ai_generated=true applies the AI info label. media_type=VIDEO has been unsupported since November 9, 2023 — single videos are published as REELS.

How to publish a post via the Instagram API, step by step

  1. Host the media publicly

    Put the file on a public HTTPS URL with no authentication. Meta cURLs the media during the publishing attempt, so the URL must work at that exact moment.

  2. Create the container

    POST /<IG_ID>/media with image_url (or media_type=REELS and video_url) and an optional caption. The response is the container ID.

  3. Wait for processing

    For videos and carousels, GET /<CONTAINER_ID>?fields=status_code until it returns FINISHED. Meta recommends polling at most once per minute for no more than 5 minutes.

  4. Publish

    POST /<IG_ID>/media_publish with creation_id=<CONTAINER_ID>. The ID in the response is the published Instagram media ID.

  5. Read the result

    Request the media fields (such as permalink) by the new ID and store a link to the post.

What the raw requests look like

Platform-level calls against the Instagram Login host. For Facebook Login, switch the host to graph.facebook.com and use a Page token.

publish.sh
# 1. Container for a JPEG image
curl -X POST "https://graph.instagram.com/v23.0/$IG_ID/media" \
  -H "Authorization: Bearer $IG_TOKEN" \
  -d "image_url=https://cdn.example.com/photo.jpg" \
  --data-urlencode "caption=New collection is live"
# -> {"id": "<CONTAINER_ID>"}

# 2. Check status (needed for videos and carousels)
curl -G "https://graph.instagram.com/v23.0/$CONTAINER_ID" \
  -H "Authorization: Bearer $IG_TOKEN" -d "fields=status_code"

# 3. Publish
curl -X POST "https://graph.instagram.com/v23.0/$IG_ID/media_publish" \
  -H "Authorization: Bearer $IG_TOKEN" -d "creation_id=$CONTAINER_ID"

Publishing with the Socialift SDK

publish.py
import asyncio, os
from socialift import Instagram

CDN = "https://cdn.example.com"

async def main():
    token = os.environ["IG_USER_TOKEN"]
    async with Instagram.from_env() as ig:
        photo = await ig.publish(token, image_url=f"{CDN}/photo.jpg", caption="New collection")
        reel = await ig.publish(token, video_url=f"{CDN}/teaser.mp4", is_reel=True, caption="Behind the scenes")
        album = await ig.publish_carousel(
            token, image_urls=[f"{CDN}/1.jpg", f"{CDN}/2.jpg", f"{CDN}/3.jpg"], caption="Lookbook"
        )
        post = await ig.media_details(photo["media_id"], token)
        print(post.get("permalink"), reel["media_id"], len(album["children"]))

asyncio.run(main())

publish creates the container and publishes it, returning container_id and media_id. For videos it polls status_code every 2 seconds for up to 60 seconds and raises ApiError on ERROR or timeout — long Reels may need a retry in your own background job.

Always pass is_reel=True for video: Meta no longer accepts media_type=VIDEO.

publish_carousel creates child containers in parallel, waits for the parent container and publishes. It takes image URLs only, keeps the first 10 and raises ConfigurationError for fewer than 2.

For Facebook Login use Instagram.via_facebook(app_secret=...), the Page token and ig_user_id=. Stories, alt_text, share_to_feed and video carousel items aren't parameters of these methods — call the Graph API directly for those.

How many posts per day can you publish via the API?

Instagram accounts are limited to 100 API-published posts within a moving 24-hour period, and a carousel counts as a single post. There is no midnight reset: the window slides continuously.

Check current usage with GET /<IG_ID>/content_publishing_limit. The response contains quota_usage (posts published in the window) and a config object with quota_total and quota_duration; the optional since parameter takes a Unix timestamp no older than 24 hours. Don't hard-code the number — read quota_total from the response.

Containers have their own ceiling: an account can create 400 containers in a rolling 24 hours. An unpublished container expires after 24 hours, so don't create them far in advance.

Container status codes

Container status codes — Capability matrix by platform
status_codeMeaningWhat to do
IN_PROGRESSThe container is still being processedPoll again later
FINISHEDThe container is ready to publishCall media_publish
PUBLISHEDThe container's media has already been publishedDon't publish it again
ERRORProcessing failedCheck the media against the specs and create a new container
EXPIREDNot published within 24 hoursCreate a new container

Common publishing errors and how to avoid them

  • The URL needs auth, redirects to a login page or has expired (signed URLs) — Meta can't fetch the file.
  • The image is PNG, WebP or another format — convert it to JPEG first.
  • The aspect ratio is outside 4:5–1.91:1 — crop before uploading.
  • media_publish was called before the video or carousel container reached FINISHED.
  • media_type=VIDEO was sent — use REELS for single videos.
  • More than 10 carousel children, or a caption over 2,200 characters, 30 hashtags or 20 mentions.
  • A bulk import hit the 100-post window — check content_publishing_limit first.
  • Expecting shopping tags, filters or story stickers — the API doesn't support them.

← Back to blog

Frequently asked questions

How many posts per day can I publish with the Instagram API?

One account can publish 100 posts through the API within a rolling 24 hours, and a carousel counts as one. GET /<IG_ID>/content_publishing_limit returns the current usage and the limit value.

Can I publish Instagram Stories via the API?

Yes, with a container using media_type=STORIES. Link, poll and location stickers aren't supported, and Meta's Facebook Login docs limit story publishing to business accounts. The Socialift SDK has a dedicated method for it, publish_story() (since 1.3.0); publish() only posts to the feed.

Can a carousel mix photos and videos?

Yes. A carousel accepts up to 10 images, videos or a mix, each created as its own container with is_carousel_item=true. The SDK's publish_carousel, however, works with images only.

Why does Instagram reject my image?

Usually the format: only JPEG is accepted, up to 8 MB, with an aspect ratio from 4:5 to 1.91:1. The other common cause is a URL that is private or wasn't reachable during publishing, since Meta downloads the file at that moment.

Can I schedule Instagram posts through the API?

The documented flow publishes when you call media_publish, so scheduling is normally built in your own backend with a queue or cron. Containers expire after 24 hours — create them close to the publish time.

Sources

  1. Meta: Publish content using the Instagram Platform
  2. Meta: IG User Media reference
  3. Meta: Instagram Platform changelog
  4. Meta: Instagram API with Facebook Login

Related pages