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

How to post to Instagram via the Graph API: prerequisites, the container, status, and publish calls, and curl for images, Reels, Stories, and carousels.

Canonical page: https://www.tryadeli.com/blog/post-to-instagram-via-api
Last updated: 2026-10-05

By Mika Reyes, Co-founder, Adeli. Published 2026-10-05.

## 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](https://www.tryadeli.com/blog/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](https://developers.facebook.com/docs/instagram-platform/content-publishing) 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](https://developers.facebook.com/docs/instagram-platform/content-publishing) and the [IG User media reference](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-user/media).

## 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](https://developers.facebook.com/docs/instagram-platform/content-publishing). 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](https://developers.facebook.com/docs/instagram-platform/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](https://www.tryadeli.com/blog/meta-app-review-time) 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](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-user/media)). 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:

```bash
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:

```json
{ "id": "<IG_CONTAINER_ID>" }
```

Feed images have to match Meta's [image spec](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-user/media):

- **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:

```bash
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](https://developers.facebook.com/docs/instagram-platform/content-publishing)). The status values are listed in the [container reference](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-container).

## Step 3: Publish with media_publish

Once the status is `FINISHED`, publish the container:

```bash
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:

```json
{ "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.

```bash
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](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-user/media):

| 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`:

```bash
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.

```bash
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](https://developers.facebook.com/blog/post/2023/05/16/introducing-stories-publishing-to-the-content-publishing-api-on-instagram/) 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`:

```bash
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>"}'
```

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](https://developers.facebook.com/docs/instagram-platform/content-publishing). 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:

```bash
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](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-user/content_publishing_limit) 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](https://www.tryadeli.com/blog/is-instagram-api-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](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/error-codes):

| 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](https://www.tryadeli.com/blog/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](https://www.tryadeli.com/product/instagram) 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](https://www.tryadeli.com/social-media-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](https://www.tryadeli.com/pricing), **your first 3 connected accounts are free**, with no credit card. [Sign in](https://app.tryadeli.com/sign-in) and you can create your API key straight away.

## 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.

## Related Adeli pages

- [Instagram API for Developers](https://www.tryadeli.com/product/instagram.md): Publish, schedule, and read analytics for Instagram through one API. Adeli owns the Instagram developer app, token refresh, and Meta review. Free to start.
- [Social Media Posting & Scheduling API](https://www.tryadeli.com/social-media-posting-api.md): Adeli's social media posting and scheduling API publishes to Instagram, TikTok, YouTube, Facebook, and X in one request, now or at a set time. Free to start.
- [All posts on the Adeli blog](https://www.tryadeli.com/blog.md)

## About the author

Mika Reyes is the CEO behind Adeli and a tech and AI content creator. She was previously co-founder and CEO of Parallax, which was acquired by Phantom, and a product lead at LinkedIn. She is a Forbes 30 Under 30 honoree.
