Post to Instagram via API: Images, Reels, Stories, Carousels
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_IDin 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.
- Create a container with
POST /<IG_ID>/media. You get back a container ID. - Check its status with
GET /<IG_CONTAINER_ID>?fields=status_codeuntil it readsFINISHED. - Publish it with
POST /<IG_ID>/media_publishand 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_tagsis supported. - No
alt_textorlocation_idon 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.
- Create a child container for each item with
image_urlorvideo_urland"is_carousel_item": true. No caption on the children. - Create the carousel container with
media_type=CAROUSEL, the caption, and the child IDs inchildren:
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>"}'- 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.