Skip to main content
Before you start. Instagram requires a Business or Creator account. Personal accounts cannot publish via the Content Publishing API regardless of scopes granted — the connect flow rejects them with instagram.account_type.requires_professional and a remediation pointing at the Switch to professional account toggle in the IG app.

Quick reference

Connect an account

Instagram has its own OAuth (since the 2024 Instagram Login product) — distinct from Facebook Login. There is no FB fan-out anymore; connecting Instagram does not also connect Facebook. POST /v1/accounts/connect/instagram produces an authorize URL at instagram.com/oauth/authorize. After the user grants, the callback runs three Graph calls:
  1. Exchange code → short-lived token (1 h).
  2. Swap short-lived → long-lived (60 d).
  3. GET /me?fields=id,user_id,username,account_type — the Content Publishing API expects id (the IG-scoped user id), not user_id — the provider persists id as platformAccountId. If account_type === "PERSONAL" the connect is rejected.

Scopes

extended scopes (off by default) cover comment + insight reads — not required for publishing.

Token lifecycle

Long-lived tokens last 60 days and are refreshable any time after the first 24 hours via GET /refresh_access_token. The provider refreshes on schedule; token.expiring fires before expiry.

Post types

Single image

single-image.json

Single video (Reels)

Single-video posts route through the REELS container — Instagram retired the legacy VIDEO product surface in 2024.
reels.json
Carousel children can mix image and video items.
carousel.json
The publisher creates each child container (is_carousel_item=true), polls each to FINISHED, then creates a CAROUSEL parent with the child ids and publishes the parent.

Wisdom (platform-specific things that bite)

  • JPEG only for images. PNG, WebP, HEIC, and GIF will be rejected with code 100, error_subcode 2207003. Preflight catches this locally so you don’t lose a roundtrip — re-encode to JPEG via your image pipeline (or upload via POST /v1/media which preserves the source mime).
  • Media URLs must be publicly reachable by Meta’s CDN — anonymous HTTPS, no auth, no Drive links, no signed S3 URLs that expire before the container finishes. Unreachable URLs surface as OAuthException 2207052, the canonical opaque-rejection.
  • The first 125 characters of the caption show before the “more” fold in feed. Front-load anything important.
  • Container expiry: Instagram’s media containers live for 24 hours after creation. The publisher creates + publishes in one synchronous flow so this rarely matters, but a scheduled post that ages out triggers instagram.container.expired.
  • The Content Publishing API uses id (the IG-scoped user id) on /{ig-user-id}/medianot user_id from the token-exchange response. The provider stores the right one; if you ever rebuild this manually, sending user_id returns code:2.
  • Reels duration is platform-enforced. We size-check at preflight, but the 90 s ceiling is checked by Instagram at container-finalize and surfaces as a platform rejection.
  • The publisher’s response carries a permalink fetched best-effort via GET /{media-id}?fields=permalink. If that call 404s, the publish still succeeds and the response carries the id alone.

Common errors

What you can’t do (yet)

  • Stories (Stories use a separate STORIES container type not in v1).
  • Direct messages.
  • First comment as the author.
  • Tagging users or product tags.
  • Editing or deleting a published post.
  • Posting from a Personal Instagram account (Meta’s API restriction — switch to Business or Creator).
  • Reading comments, replies, or insights (requires extended scopes plus read endpoints not in v1).

API reference