Home
Blog
How much does the X (Twitter) API actually cost in 2026?
Threads API tutorial: publishing posts and carousels programmatically
How to post to Instagram via API: the complete 2026 guide
Case study: how Aveiro ships social publishing on Chirio
A 201 is not a promise: designing for mixed results
One API for social publishing
Sponsored
Aveiro
aveiro.app
TrademarkTrademark
Ctrl k
Search...
Sign up

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.
Updated 12d ago
Threads API tutorial: publishing posts and carousels programmatically
Case study: how Aveiro ships social publishing on Chirio
Sponsored
Aveiro
aveiro.app
Sponsored
Aveiro
aveiro.app
TrademarkTrademark
DoplerChirioDocs
© Dopler. All rights reserved.
Built with Aveiro
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 App Review plus Business Verification — plan for weeks, not days. 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.

The one-call alternative

Chirio wraps everything above — hosted OAuth, token refresh, the container flow, carousel orchestration, processing polls, retry-safe idempotency — behind one API: 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());