Blog

Instagram Content Publishing API: Accounts, Permissions, and Containers

Choose the right Instagram login flow, request the publishing permissions it expects, and move media safely through containers and media_publish.

Kavenio Engineering

Related implementation guides

  1. Why Instagram Publishing Fails Before Upload: An Account and Permission Checklist
  2. Build an Instagram API Post With an Image: OAuth to Final Status

Instagram publishing usually breaks before Instagram has a post to publish.

The request to media_publish is only the final step. Before that can work, the account must be eligible, the app must use the right login configuration, the token must carry the matching permissions, Meta must be able to fetch the media, and the media container must finish processing.

This guide is for engineers adding Instagram publishing to a SaaS product, automation service, or internal platform. It covers the decisions that have to be correct before the first API-published post can exist.

Start with the login configuration

Meta currently provides two configurations for professional Instagram accounts. They both publish content, but they do not use the same login flow, token, host, or permission names.

DecisionInstagram LoginFacebook Login for Business
Eligible Instagram accountsBusiness and CreatorBusiness and Creator linked to a Facebook Page
User signs in withInstagram credentialsFacebook credentials
Facebook Page requiredNoYes
Access token used for publishingInstagram User tokenFacebook Page token
API hostgraph.instagram.comgraph.facebook.com
Publishing scopesinstagram_business_basic, instagram_business_content_publishinstagram_basic, instagram_content_publish, pages_read_engagement
Best fitProducts that need Instagram publishing without requiring a PageProducts that also need Page-linked features such as hashtag search, product tagging, or partnership-ad capabilities

Meta documents the distinction in its Instagram Platform overview and its separate guides for Instagram Login and Facebook Login.

Do not combine the two permission sets. An Instagram User token with instagram_business_content_publish belongs to the Instagram Login path. A Page token with instagram_content_publish belongs to the Facebook Login path. The similar names hide two different authorization models.

Kavenio uses Instagram Login for its Instagram connection flow. It requests instagram_business_basic and instagram_business_content_publish, exchanges the authorization code for a short-lived token, exchanges that for a long-lived token, and reads the professional account identity from graph.instagram.com.

A successful test account is not production access

Both login configurations support Business and Creator accounts. Consumer accounts are not eligible for the publishing API.

Account type is only the first gate. Meta also separates app access into two levels:

  • Standard Access is for accounts owned or managed by people who have a role on the app. It is useful during development and for an app that serves only accounts its team controls.
  • Advanced Access is required when the app serves professional accounts owned by customers who do not have a role on the app. Advanced Access requires App Review and Business Verification.

That distinction matters for SaaS products. Publishing successfully to the developer's own Business account proves the request shape and token work. It does not prove that an unrelated customer can authorize the same app in production. Meta explains this boundary in the access-level section of the Instagram Platform overview.

For Facebook Login, the customer also needs an appropriate task on the Page linked to the Instagram professional account. A user who can view a Page is not automatically allowed to grant content-publishing access for it.

The publish operation is a state machine

Instagram does not accept a binary image or video and immediately return a published post. The normal flow creates a media container first and publishes that container only after it is eligible.

professional account
        |
        v
POST /{ig-user-id}/media
        |
        v
media container id
        |
        v
GET /{container-id}?fields=status_code
        |
        +--> IN_PROGRESS -- wait and check again
        |
        +--> ERROR ------- stop and preserve the provider response
        |
        +--> EXPIRED ----- create a new container
        |
        v
     FINISHED
        |
        v
POST /{ig-user-id}/media_publish
        |
        v
published Instagram media id

Meta's Content Publishing guide defines five container states:

  • IN_PROGRESS: Meta is still processing the media.
  • FINISHED: the container is ready for media_publish.
  • ERROR: processing failed.
  • EXPIRED: the container was not published within 24 hours.
  • PUBLISHED: the container's media has already been published.

Treat those as states, not generic success and failure booleans. Retrying media_publish while a video is still IN_PROGRESS does not make processing finish sooner. Reusing an EXPIRED container cannot work. Publishing a PUBLISHED container again risks turning a recovery path into a duplicate-post path.

Kavenio follows this sequence for Instagram feed posts, carousels, Reels, and Stories: create the required container or child containers, poll provider status, and then call media_publish. The public Kavenio Instagram documentation shows the supported content types and request fields.

A minimal image-publishing request

For Instagram Login, a single-image publish starts by creating a container on graph.instagram.com. Keep the API version configurable rather than copying a version from an old example.

curl -X POST \
  "https://graph.instagram.com/${API_VERSION}/${IG_USER_ID}/media" \
  -H "Authorization: Bearer ${INSTAGRAM_ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://cdn.example.com/launch.jpg",
    "caption": "Launch day"
  }'

The response contains a container ID, not the final post ID.

{
  "id": "17889455560051444"
}

Check the container before publishing it:

curl \
  "https://graph.instagram.com/${API_VERSION}/${CONTAINER_ID}?fields=status_code" \
  -H "Authorization: Bearer ${INSTAGRAM_ACCESS_TOKEN}"

Once the status is FINISHED, publish the container:

curl -X POST \
  "https://graph.instagram.com/${API_VERSION}/${IG_USER_ID}/media_publish" \
  -H "Authorization: Bearer ${INSTAGRAM_ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{\"creation_id\":\"${CONTAINER_ID}\"}"

The ID returned by this final request is the published Instagram media ID. Store it separately from the container ID. They represent different objects and are useful for different recovery checks.

Meta must be able to fetch the media

The image_url and video_url parameters are not upload instructions. They tell Meta where to fetch the file. At publish time, the URL must be reachable from the public internet without a session cookie, private-network access, short-lived browser header, or expired signature.

Before creating a container, verify at least:

  • the URL uses HTTPS and resolves publicly;
  • redirects end at the media file rather than an HTML login page;
  • the response returns the expected Content-Type;
  • a signed URL remains valid long enough for Meta to fetch and process it;
  • the actual file matches the media metadata your application validated.

For a scheduled post, "public now" is not enough. The URL has to remain public when the scheduled job creates the container.

Carousels add another container layer

A carousel uses one child container for each image or video. After the children are ready, the app creates a parent CAROUSEL container whose children field contains those child IDs. The parent container is the one sent to media_publish.

image/video URLs
      |
      v
2-10 child containers
      |
      v
parent CAROUSEL container
      |
      v
media_publish

If one child fails, the parent is not a partial success. Preserve the failed child's provider response, stop the parent publish, and decide whether the user should replace that item or retry the complete carousel.

Check the publishing limit before a queue reaches it

Meta's current publishing guide states that an Instagram professional account can publish up to 100 API-published posts in a moving 24-hour period. A carousel counts as one published post. The API exposes the account's current usage at /{ig-user-id}/content_publishing_limit.

Query the endpoint instead of treating a copied number as permanent. A scheduler can know that ten posts are due; only the provider can report the current account-level publishing usage.

Diagnose the layer that actually failed

SymptomLayer to inspectUseful next check
OAuth completes, but the account cannot publishAccount eligibilityConfirm the account is Business or Creator
Your account works, but unrelated customers cannot connectApp accessConfirm Advanced Access, App Review, and Business Verification
The token is accepted, but /media returns a permission errorLogin configuration and scopesMatch the token host and permission names to the chosen login flow
Container creation succeeds, but status stays IN_PROGRESSProvider processingPoll deliberately; do not call media_publish early
Container status is ERRORMedia processingPreserve the provider response and validate the fetched file
Container status is EXPIREDContainer lifecycleCreate a new container; do not reuse the old ID
Meta cannot fetch the URLMedia hostingCheck public access, redirects, TLS, content type, and expiry
media_publish rejects a busy accountPublishing limitQuery content_publishing_limit and move queued work

The important separation is simple: authentication proves who granted access, permissions define what the token may do, and a media container represents work Meta still has to process. Treating all three as one "Instagram API call" makes failures hard to explain and harder to recover from.

Kavenio currently uses the direct Instagram Login path and owns the container sequence for supported Instagram posts. If a product needs Page-linked features available only through Facebook Login, that is a separate integration decision, not an option that should be assumed from a successful publish request.

Build social posting into your product

Connect accounts, validate content, publish posts, and receive status events through one posting-focused API