How to post to Instagram via API: the complete 2026 guide
Everything required to publish to Instagram programmatically in 2026 — app setup without a Facebook Page, the container publish flow, JPEG-only gotchas, carousels, Reels, App Review — and the one-call alternative.
Publishing to Instagram from your own code is entirely possible in 2026 — but the path is littered with requirements that aren't obvious until something fails: professional accounts only, JPEG-only images, a two-step container flow, redirect URIs that must match to the character, and an App Review process standing between your prototype and real users.This guide walks the whole path with the raw Instagram API first, then shows the one-call alternative. Both work; they just cost very different amounts of your time.
What you need before writing any code
Three things are non-negotiable:
An Instagram professional account. Only Business or Creator accounts can publish via the API. Personal accounts cannot — the OAuth flow will connect, but publishing fails.
A Meta app with the right use case. At developers.facebook.com, create an app with the "Instagram API with Instagram Login" use case. This is the newer flow: unlike the old Facebook-Login path, no Facebook Page is required.
HTTPS redirect URIs. Meta requires HTTPS in live mode. For local development, http://localhost:3000 is accepted in some places; otherwise use a tunnel like cloudflared or ngrok.
Copy the Instagram App ID and Secret (note: these differ from the app's main Meta App ID) and register your redirect URI under the Business Login settings.
The redirect URI trap
If your deployed app builds its redirect URI from an unset or wrong base URL, Meta won't give you a clear error — you'll see Instagram's own 'Invalid redirect_uri' page. The URI you send at authorize time must match a registered one exactly, scheme and all.
Step 1: OAuth — getting a token for the account
The scopes that matter for publishing are instagram_business_basic and instagram_business_content_publish. In development mode, only accounts with a role on your app (or invited testers) can connect. Serving external customers requires — plan for weeks, not days.
App Review plus Business Verification
Exchange the authorization code for a short-lived token, then exchange that for a long-lived token (60 days). You own the refresh schedule: refresh before expiry or the account silently stops publishing and your user has to reconnect.
Step 2: The container publish flow
Instagram publishing is a two-step (sometimes three-step) dance. You never send bytes directly — you send publicly accessible URLs that Meta fetches at publish time.
Single image:
The gotchas that will actually bite you:
Images must be JPEG. PNG and WebP are rejected. If your pipeline produces PNGs, convert before publishing.
A single video publishes as a Reel. There is no plain-video feed post anymore.
Videos process asynchronously. After creating a video container, poll its status_code until FINISHED — this can take minutes. Publishing an unfinished container fails.
Caption limit is 2,200 characters, and each account is capped at 100 API-published posts per 24 hours.
Step 3: Carousels — containers all the way down
A carousel (2–10 items) multiplies the container work. Create one child container per item with is_carousel_item=true, wait for each child to finish processing, then create a parent container with media_type=CAROUSEL listing the children, then publish the parent. Video children are allowed inside carousels.
One failed child means no partial carousel — you fail the whole post and clean up. With mixed video children, a single carousel publish can take several minutes end to end, which is exactly when clients time out and retry. If you build this yourself, build idempotency with it, or you will double-post.
Send 2–10 mediaItems and they publish as a carousel in array order; send one video and it publishes as a Reel; retry with the same idempotencyKey and nothing double-posts. Validation (JPEG-only, caption limits, item counts) happens up front with clear errors instead of opaque platform failures.
Which path should you take?
Build directly on the Instagram API if social publishing is your product and you need every platform feature the day it ships. Use a publishing API if Instagram is one feature among many in your app — the ongoing cost isn't the first integration, it's staying current: token refresh crons, API version bumps, App Review renewals, and the next undocumented behavior change.
Either way, you now know where the bodies are buried.
# 1. Create a media container
curl -X POST "https://graph.instagram.com/v23.0/{ig-user-id}/media" \
-d "image_url=https://cdn.example.com/photo.jpg" \
-d "caption=Launch day!" \
-d "access_token={token}"
# → { "id": "{container-id}" }
# 2. Publish the container
curl -X POST "https://graph.instagram.com/v23.0/{ig-user-id}/media_publish" \
-d "creation_id={container-id}" \
-d "access_token={token}"
// 1. Connect the account (hosted OAuth — your user clicks a link)
const { authUrl } = await fetch(`${CHIRIO}/api/v1/accounts/connect`, {
method: "POST",
headers,
body: JSON.stringify({ platform: "instagram", redirectUrl: "https://yourapp.com/settings" }),
}).then((r) => r.json());
// 2. Publish — single image, carousel, or Reel, same call
const { post } = await fetch(`${CHIRIO}/api/v1/posts`, {
method: "POST",
headers,
body: JSON.stringify({
content: "Launch day!",
mediaItems: [{ type: "image", url: "https://cdn.example.com/photo.jpg" }],
platforms: [{ platform: "instagram", accountId }],
idempotencyKey: "launch-post-1",
}),
}).then((r) => r.json());