Post to Instagram via API: Images, Reels, Stories, Carousels

By Mika ReyesPublished 8 min read

The short answer

You can post to Instagram via API with Meta's Instagram Graph API, for Business and Creator accounts only. Every format takes three calls: create a media container from a public URL, wait for its status to read FINISHED, then call media_publish. Reels use media_type=REELS, Stories use STORIES, and carousels combine child containers. Each account can publish 100 posts per 24 hours. Adeli publishes all four formats through one API key.

To post to Instagram via API, you use Meta's Instagram Graph API and its content publishing flow: create a media container, check that it's ready, then publish it. It works for Business and Creator accounts, and the same three calls cover single images, Reels, Stories, and carousels. Below are the curl requests from Meta's docs for each format, then the posting limit and the errors you're most likely to hit.

For app setup, logins, and access tokens, start with our Instagram API guide. This post picks up where that one leaves off, at the first publish call.

Can you post to Instagram via API?#

Yes. Meta's content publishing API lets an approved app publish to Instagram professional accounts. It supports four formats:

Format How you create it Media
Single image image_url JPEG
Reel media_type=REELS + video_url MOV or MP4
Story media_type=STORIES + image_url or video_url JPEG, MOV, or MP4
Carousel media_type=CAROUSEL + children 2 to 10 images, videos, or a mix

A few things can't be published through the API: shopping tags, filters, and Story stickers (link, poll, and location stickers). Meta lists these in the content publishing limitations and the IG User media reference.

What do you need before you start?#

You need four things in place before the first call works:

  • A professional account. Business or Creator. Personal accounts can't be published to.
  • A token with the publishing permission. The permission name depends on the login your app uses (table below).
  • Publicly hosted media. Meta fetches the file from your URL, so it has to be "hosted on a publicly accessible server at the time of the attempt," per the docs. A signed URL that expires in seconds, or a file behind auth, fails.
  • The account's Instagram user ID (IG_ID in the examples), which you get back after login.
Instagram API with Instagram Login Instagram API with Facebook Login
Host graph.instagram.com graph.facebook.com
Token Instagram User access token Facebook Page access token
Permissions instagram_business_basic, instagram_business_content_publish instagram_basic, instagram_content_publish, pages_read_engagement

Source: Content publishing. The examples below use Instagram Login and API version v25.0, as Meta's docs do. For Facebook Login, swap the host to graph.facebook.com.

Testing on your own account needs only Standard Access. Publishing to your customers' accounts needs Advanced Access, which means App Review. Our post on how long Meta app review takes covers the timeline.

How does the Instagram content publishing API work?#

Every format follows the same three steps. You describe the post in a container, Meta downloads and processes the media, and you publish the container once it's ready.

  1. Create a container with POST /<IG_ID>/media. You get back a container ID.
  2. Check its status with GET /<IG_CONTAINER_ID>?fields=status_code until it reads FINISHED.
  3. Publish it with POST /<IG_ID>/media_publish and the container ID. You get back the published media ID.

Containers expire after 24 hours, and an account can create 400 containers per rolling 24 hours (IG User media reference). Create the container close to when you plan to publish.

Step 1: Create a media container#

For a single image, send the public JPEG URL and an optional caption:

curl -X POST "https://graph.instagram.com/v25.0/<IG_ID>/media" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -d '{"image_url":"https://www.example.com/images/photo.jpg","caption":"Posted via the API"}'

The response is the container ID:

{ "id": "<IG_CONTAINER_ID>" }

Feed images have to match Meta's image spec:

  • Format: JPEG only. Convert PNG and WebP before you send them.
  • Size: 8 MB maximum, 320 to 1440 pixels wide.
  • Aspect ratio: between 4:5 and 1.91:1.
  • Caption: up to 2,200 characters, 30 hashtags, and 20 @ tags.

The same call accepts optional fields such as alt_text, user_tags, location_id, collaborators (up to 3 usernames), and is_ai_generated to apply Instagram's AI label.

Step 2: Check the container status#

Meta processes the media after you create the container, so don't publish right away. Query the status:

curl "https://graph.instagram.com/v25.0/<IG_CONTAINER_ID>?fields=status_code" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
status_code What it means What to do
IN_PROGRESS Meta is still processing the media Wait and check again
FINISHED Ready to publish Call media_publish
ERROR Processing failed Read the error subcode in status, fix the media, make a new container
EXPIRED Not published within 24 hours Make a new container
PUBLISHED Already published Nothing; don't publish it twice

Meta recommends checking once per minute, for no more than 5 minutes (content publishing guide). The status values are listed in the container reference.

Step 3: Publish with media_publish#

Once the status is FINISHED, publish the container:

curl -X POST "https://graph.instagram.com/v25.0/<IG_ID>/media_publish" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -d '{"creation_id":"<IG_CONTAINER_ID>"}'

The response is the ID of the live post:

{ "id": "<IG_MEDIA_ID>" }

Store that ID. You'll need it to read the post's insights, manage its comments, or fetch its permalink later.

How do I post Reels with the Instagram API?#

Set media_type=REELS and pass a video_url. The rest of the flow is identical: container, status, publish.

curl -X POST "https://graph.instagram.com/v25.0/<IG_ID>/media" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -d '{"media_type":"REELS","video_url":"https://example.com/video.mp4","caption":"First Reel via the API"}'

Reels have their own video spec:

Spec Reels requirement
Container MOV or MP4, moov atom at the front, no edit lists
Codecs H.264 or HEVC video, AAC audio
Frame rate 23 to 60 FPS
Aspect ratio 0.01:1 to 10:1, with 9:16 recommended
Duration 3 seconds to 15 minutes
File size 300 MB maximum

Reels-only options include share_to_feed (show the Reel in the Feed tab as well as Reels), cover_url for a custom JPEG cover, thumb_offset to pick a cover frame in milliseconds, and audio_name to name the audio.

Large files can use a resumable upload. Add "upload_type":"resumable" when creating the container, then send the file to rupload.facebook.com:

curl -X POST "https://rupload.facebook.com/ig-api-upload/v25.0/<IG_MEDIA_CONTAINER_ID>" \
  -H "Authorization: OAuth <ACCESS_TOKEN>" \
  -H "offset: 0" \
  -H "file_size: <FILE_SIZE_BYTES>" \
  --data-binary "@my_video_file.mp4"

Meta's docs describe upload_type=resumable for apps built with Facebook Login for Business. Either way, poll the status before you publish. Video has to process on Meta's side before the container reads FINISHED.

How do I post a story with the Instagram API?#

Set media_type=STORIES and pass an image_url or video_url. Then check the status and publish, as with any other container.

curl -X POST "https://graph.instagram.com/v25.0/<IG_ID>/media" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -d '{"media_type":"STORIES","image_url":"https://www.example.com/images/story.jpg"}'

Stories come with more limits than the other formats:

  • No stickers. Link, poll, and location stickers can't be published. Mentioning users without a sticker works, and user_tags is supported.
  • No alt_text or location_id on Stories.
  • Video Stories run 3 to 60 seconds, up to 100 MB. Image Stories are JPEG, up to 8 MB. 9:16 is recommended for both.
  • Account type. Meta's launch post for Stories publishing said only Business accounts could publish Stories through the API. Meta's current docs don't restate the rule, so test with a Creator account before you depend on Stories for one.

If you report on published media, note that a Story's media_type comes back as IMAGE or VIDEO. Read media_product_type to tell a Story apart from a feed post.

How do I post a carousel with the Instagram API?#

A carousel takes one container per item, then one parent container that lists them. That makes it the only format with more than three calls.

  1. Create a child container for each item with image_url or video_url and "is_carousel_item": true. No caption on the children.
  2. Create the carousel container with media_type=CAROUSEL, the caption, and the child IDs in children:
curl -X POST "https://graph.instagram.com/v25.0/<IG_ID>/media" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -d '{"media_type":"CAROUSEL","caption":"Three photos, one post","children":"<CHILD_ID_1>,<CHILD_ID_2>,<CHILD_ID_3>"}'
  1. Check the status and publish the carousel container with media_publish, as in Steps 2 and 3.

Build around these rules:

  • 2 to 10 items. Fewer than 2 or more than 10 returns error 2207028.
  • Cropping follows the first item. Meta crops every image to the first image's aspect ratio, 1:1 by default.
  • It counts as one post against the publishing limit.

What is the Instagram API posting limit?#

100 API-published posts per account in a rolling 24 hours, according to Meta's content publishing guide. A carousel counts as one post. Separately, an account can create 400 containers in the same window.

Check an account's usage before you publish with the content_publishing_limit endpoint:

curl "https://graph.instagram.com/v25.0/<IG_ID>/content_publishing_limit?fields=quota_usage,rate_limit_settings" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"

quota_usage is how many posts the account has published in the window. The example response in Meta's endpoint reference shows a quota_total of 50, which doesn't match the guide's 100, so read config.quota_total from the response rather than hardcoding a number. When an account hits the cap, publishing fails with error 2207042 until the window rolls forward.

API call limits are a separate budget. Our post on whether the Instagram API is free covers those.

What are the common Instagram publishing errors?#

Most publishing errors come from the media itself or from publishing too early. These are the ones you're most likely to see, from Meta's error code reference:

Subcode Meta's message (short) Usual cause and fix
2207052 Media could not be fetched from this URI URL isn't public or has expired. Host the file where Meta can reach it.
2207027 Media is not ready for publishing You published before FINISHED. Poll the status first.
2207009 Aspect ratio cannot be published Image is outside 4:5 to 1.91:1. Crop or pad it.
2207004 Image is too large to download Over 8 MB. Compress it.
2207026 Video format is not supported Re-encode as MP4 or MOV with H.264 or HEVC and AAC.
2207010 Caption is too long Over 2,200 characters, 30 hashtags, or 20 @ tags.
2207028 Won't work as a carousel Fewer than 2 or more than 10 items.
2207042 Reached maximum number of posts The account hit the 24-hour publishing limit. Retry later.
2207008 Media builder does not exist or has expired Retry once or twice within 2 minutes, then create a new container.
2207050 The Instagram account is restricted The user has to resolve it in the Instagram app.

If a call fails with a permissions error instead, the token is missing the publishing permission, has expired, or was revoked. The Instagram API guide covers token refresh.

Is there a faster way to post to Instagram via API?#

Yes, if you'd rather not own the Meta app. Everything above assumes you've already built the parts around it: a Meta developer app, a login flow, Advanced Access through App Review, public media hosting, status polling, and a refresh job for every connected account's token.

App review is usually the slowest part. When we went through Meta's review for the publishing permission, the screencast got far more scrutiny than the written use case. Expect reviewers to check that the video shows the full login, the user granting the permission on the consent screen, and the post appearing live on Instagram. A success message in your own app isn't enough. If your text claims a format the recording doesn't show, expect a rejection.

Adeli's Instagram API takes that work off your team. Adeli maintains the Instagram developer app, goes through Meta's review, and refreshes every connected account's tokens, so you hold one API key. It publishes feed posts, Reels, Stories, and carousels, and each of your users connects their own Instagram account.

The same request through Adeli's posting API can also publish to TikTok, YouTube, Facebook, and X. You get a per-platform status on the response and a webhook when anything changes, so a failed Instagram post doesn't hold up the other networks.

On Adeli's pricing, your first 3 connected accounts are free, with no credit card. Getting started takes a short onboarding call, and then you get your API key.

faq

Frequently asked questions

Can I post to a personal Instagram account via API?

No. The Instagram Graph API only publishes to professional accounts, meaning Business or Creator accounts. A personal account has to switch to a professional account in the Instagram app before any app can post to it.

Do I need Meta app review to post to Instagram via API?

Only if you post to accounts you don't own or manage. Standard Access covers your own accounts, which is enough for testing. A product where customers connect their own Instagram needs Advanced Access for the publishing permission, and that means App Review and Business Verification.

Is the Instagram content publishing API free?

Yes. Meta charges nothing to call it. The cost is time: app review, public media hosting, status polling, and refreshing every account's access token before it expires.

Can I schedule Instagram posts through the API?

Adeli can. Send a scheduled time instead of publishNow and the post waits in Adeli's queue until it's due, and you can list, update, or cancel queued posts through the API. If you build on Meta's API directly, you run the queue yourself and call media_publish when each post is due.

Power your next project
with one social media API

Give your users publishing, scheduling, and analytics through one integration your team can actually maintain.

Get started

Start with free credits. No credit card required.