Instagram API: How to Post With Code in 2026

The Instagram API explained: account and login requirements, the 3-step container flow, the 100-post limit, and runnable code to publish images, Reels and carousels.

InvisibleAPI Team 13 min read
View as Markdown
Instagram API: How to Post With Code in 2026
I

InvisibleAPI Team

InvisibleAPI Team

Publishing a post to Instagram from your application, script, or AI agent seems like it should be a single HTTP request: send a caption, attach an image, and receive a post URL.

Under Meta’s official Graph API, programmatic publishing works differently. Instagram publishing is an asynchronous container lifecycle. You create a media container, poll until Meta finishes downloading and encoding the asset, and then fire a separate publish request. Along the way, your code must satisfy strict account prerequisites, host media on public HTTPS servers, and navigate different workflows for images, Reels, and carousels.

This tutorial breaks down the entire process. We look at how Meta’s native container endpoints function under the hood, and how to publish programmatically using raw cURL, TypeScript, or a unified publishing API.

One API instead of the Graph API plumbing. InvisibleAPI is one job-based API for Instagram and X. Connect the account, create a publishing job, poll it until it reaches published.

Get API key

What the Instagram API is

The Instagram API is Meta’s official interface for software that works with Instagram professional accounts. It is part of Meta’s Graph API, which is why developers also call it the Instagram Graph API. Here is what it covers in 2026:

Question Answer
Which accounts? Professional accounts only (Business or Creator). Personal profiles cannot publish through the API
What can it publish? Single images, carousels of up to 10 items, and videos, which publish as Reels
What else can it do? Read account and media insights, and manage comments
How do you log in? Two paths: Instagram Login (no Facebook Page needed) or Facebook Login (needs a linked Facebook Page)
How much does it cost? Meta publishes no usage fee. The cost is the build and review work, covered in our Instagram API pricing guide
Is there a posting limit? 100 API-published posts per account in a rolling 24 hours. A carousel counts as one
Current version Graph API v26.0, released July 29, 2026

The rest of this guide covers the publishing side: the setup, the container flow, media rules, and working code.

Meta changes the platform often. Our monthly Instagram updates page tracks each change and whether it reaches the API.

Prerequisites: Account Setup and Token Requirements

Before writing a single line of publishing code, Meta requires three administrative configuration steps. Missing any one of them will trigger permissions errors ((#100) Invalid parameter or (#200) Permission denied) during API calls.

1. The Professional Account Requirement

Personal Instagram profiles cannot publish content via the API. The account must be converted to an Instagram Professional account (either Business or Creator). You can perform this switch inside the Instagram mobile app under Settings > Account Type and Tools, or through Meta Business Suite.

2. Choosing a Login Path (and When You Need a Facebook Page)

Meta offers two ways to reach the same publishing endpoints:

On the Facebook Login path, link the accounts from your Facebook Page settings under Linked Accounts, then Instagram. If that link is broken or incomplete, the Graph API will not return an Instagram User ID (ig-user-id) for your token. The permissions in the next step are the ones the Facebook Login path uses.

3. Required App Permissions and Access Tokens

Your Meta Developer App requires the following permissions to publish content:

For development, you can generate a short-lived token using the Graph API Explorer. For production systems, exchange that token for a 60-day Long-Lived User Access Token, or implement Facebook Login with OAuth 2.0 to request permissions dynamically.

How Meta’s Native API Publishing Works: The 3-Step Container Flow

Meta separates content publishing into three distinct steps because media processing takes time. Rather than holding an HTTP connection open while Instagram downloads, checks, and transcodes a video or high-resolution photo, the API uses a container lifecycle.

Step HTTP Method and Endpoint What It Does
1. Create Container POST /{ig-user-id}/media Submits media URL and caption; creates an upload container
2. Poll Status GET /{container-id}?fields=status_code Checks whether Meta has finished downloading and transcoding
3. Publish Container POST /{ig-user-id}/media_publish Publishes the verified container to the live Instagram profile

Step 1: Create the Media Container

You begin by sending a POST request to the account’s /media endpoint. You provide the image URL, the caption, and your access token.

curl -X POST "https://graph.facebook.com/v21.0/$IG_USER_ID/media" \
  -F "image_url=https://cdn.example.com/images/spring-launch.jpg" \
  -F "caption=Spring collection is live. Shop the link in bio." \
  -F "access_token=$META_ACCESS_TOKEN"

If the payload is valid, Meta returns a container ID:

{
  "id": "17895695634123456"
}

This container is not visible on Instagram yet. It is a temporary holding object on Meta’s servers.

Step 2: Poll Container Status

Before publishing, you must verify that Meta has finished processing the file. Query the container’s status_code field:

curl -X GET "https://graph.facebook.com/v21.0/$CONTAINER_ID?fields=status_code&access_token=$META_ACCESS_TOKEN"

Meta returns one of four statuses:

For images, status usually transitions to FINISHED in under two seconds. For videos, transcoding can take 15 to 45 seconds depending on file size and server load.

Step 3: Publish the Container

Once the status reaches FINISHED, trigger the final publish call by passing the container ID as creation_id to the /media_publish endpoint:

curl -X POST "https://graph.facebook.com/v21.0/$IG_USER_ID/media_publish" \
  -F "creation_id=$CONTAINER_ID" \
  -F "access_token=$META_ACCESS_TOKEN"

Meta returns the live Instagram media object ID:

{
  "id": "17923485729987654"
}
The 3-step Meta container lifecycle: validation, container creation, status polling, and publication.
The 3-step Meta container lifecycle: validation, container creation, status polling, and publication.

The post is now live on the account’s Instagram profile.

Media Constraints: Public URLs, Formats, and Reels

Meta’s Content Publishing API enforces specific technical constraints on the media files you supply.

Public HTTPS URLs Only

Meta does not accept multipart binary file uploads when creating media containers. Your application must upload the image or video to an object store (such as Amazon S3, Cloudflare R2, or a public CDN) and pass that URL to Meta.

The URL must meet three requirements:

  1. It must be accessible over HTTPS.
  2. It must not require authentication headers or private cookies.
  3. The server must return correct Content-Type headers (image/jpeg or video/mp4).

Image Specifications and Aspect Ratios

Videos Publish as Reels

Every single-video upload sent through the Content Publishing API publishes as an Instagram Reel. The standalone video post format was unified with Reels across the platform.

Carousels Require Child Containers

Publishing a multi-image or mixed-media carousel adds an extra layer of container nesting:

  1. Create a child container for each individual image or video by setting is_carousel_item=true.
  2. Poll each child container until all of them report FINISHED.
  3. Create a parent container with media_type=CAROUSEL and pass an array of child container IDs in the children parameter.
  4. Publish the parent container to /media_publish.

Carousels support between 2 and 10 items and can mix images and videos in the same post.

The Single-Job Route: Publishing With InvisibleAPI

Building and maintaining the native container dance across multiple accounts introduces significant maintenance overhead: token storage, token refresh routines, Facebook Page mapping, child container orchestration, and polling loops.

InvisibleAPI provides a unified social publishing engine. Instead of orchestrating multi-step container flows per platform, you send a single asynchronous publishing job.

POST /api/v1/organizations/{organization}/publishing/jobs -> 202 Accepted

The platform validates the payload preflight, creates the necessary containers, monitors the encoding progress, and handles retryable transient errors automatically.

The Job Lifecycle

When you submit a publishing request, InvisibleAPI returns an asynchronous job object immediately:

{
  "id": "job_01JH8M5V9W2Q4K7X1P0R",
  "status": "processing",
  "clientRequestId": "blog-announcement-2026-09-21",
  "targets": ["acct_ig_8f29c41a"]
}

The job moves through documented states: pending, processing, submitted, and polling, until it reaches a terminal status:

About the API samples. targets, mediaItems, caption, clientRequestId, and the job status values are documented platform fields. The exact response envelopes and authentication headers in these examples illustrate the integration flow. Consult the Quick Start Guide for complete schema details.

Code Examples: Images, Reels, and Carousels

Here is how to publish different media types using clean, zero-dependency TypeScript.

Example 1: Publishing a Single Image (cURL and TypeScript)

First, the raw cURL request to dispatch a single image post:

curl -X POST "https://api.invisibleapi.ai/api/v1/organizations/$ORG_ID/publishing/jobs" \
  -H "Authorization: Bearer $INVISIBLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": ["acct_ig_8f29c41a"],
    "caption": "Exploring the coast. Photo taken at sunset.",
    "mediaItems": [
      {
        "mediaType": "image",
        "sourceUrl": "https://cdn.example.com/photos/coast-sunset.jpg"
      }
    ],
    "clientRequestId": "sunset-post-001"
  }'

In TypeScript, you can wrap job dispatch and status polling into a clean helper:

interface PublishResult {
  jobId: string;
  status: string;
}

export async function publishInstagramImage(
  orgId: string,
  targetAccountId: string,
  imageUrl: string,
  caption: string,
  clientRequestId: string
): Promise<PublishResult> {
  const apiKey = process.env.INVISIBLE_API_KEY;

  // 1. Submit the asynchronous publishing job
  const response = await fetch(
    `https://api.invisibleapi.ai/api/v1/organizations/${orgId}/publishing/jobs`,
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        targets: [targetAccountId],
        caption,
        mediaItems: [{ mediaType: "image", sourceUrl: imageUrl }],
        clientRequestId,
      }),
    }
  );

  if (!response.ok) {
    throw new Error(`Job creation failed: ${response.statusText}`);
  }

  const job = await response.json();
  return { jobId: job.id, status: job.status };
}

Example 2: Publishing a Video Reel

Publishing a Reel requires only changing the mediaType to video and providing an MP4 URL. Because video transcoding takes longer, your polling loop checks the job status until it resolves to published:

export async function publishInstagramReel(
  orgId: string,
  targetAccountId: string,
  videoUrl: string,
  caption: string,
  clientRequestId: string
) {
  const apiKey = process.env.INVISIBLE_API_KEY;

  const res = await fetch(
    `https://api.invisibleapi.ai/api/v1/organizations/${orgId}/publishing/jobs`,
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        targets: [targetAccountId],
        caption,
        mediaItems: [{ mediaType: "video", sourceUrl: videoUrl }],
        clientRequestId,
      }),
    }
  );

  if (!res.ok) {
    throw new Error(`Job creation failed: ${res.status} ${await res.text()}`);
  }

  const job = await res.json();
  return pollUntilComplete(orgId, job.id);
}

const TERMINAL_STATUSES = new Set([
  "published",
  "partial_failure",
  "failed",
  "action_required",
  "canceled",
]);

async function pollUntilComplete(orgId: string, jobId: string, maxAttempts = 20) {
  const apiKey = process.env.INVISIBLE_API_KEY;

  for (let i = 0; i < maxAttempts; i++) {
    await new Promise((resolve) => setTimeout(resolve, 3000));

    const pollRes = await fetch(
      `https://api.invisibleapi.ai/api/v1/organizations/${orgId}/publishing/jobs/${jobId}`,
      { headers: { "Authorization": `Bearer ${apiKey}` } }
    );
    if (!pollRes.ok) {
      throw new Error(`Job lookup failed: ${pollRes.status}`);
    }
    const data = await pollRes.json();

    if (TERMINAL_STATUSES.has(data.status)) {
      return data;
    }
  }

  throw new Error("Job polling timed out before reaching a terminal state.");
}

Example 3: Publishing a Carousel Post

To publish a carousel, pass multiple objects inside mediaItems. You can combine images and videos in the same request:

export async function publishInstagramCarousel(
  orgId: string,
  targetAccountId: string,
  items: Array<{ type: "image" | "video"; url: string }>,
  caption: string,
  clientRequestId: string
) {
  const apiKey = process.env.INVISIBLE_API_KEY;

  const response = await fetch(
    `https://api.invisibleapi.ai/api/v1/organizations/${orgId}/publishing/jobs`,
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        targets: [targetAccountId],
        caption,
        mediaItems: items.map((item) => ({
          mediaType: item.type,
          sourceUrl: item.url,
        })),
        clientRequestId,
      }),
    }
  );

  return response.json();
}

Under the hood, the API automatically generates individual child containers for each item, waits for all items to complete transcoding, constructs the parent carousel container, and executes the final publish.

Production Reliability: Idempotency, Rate Limits, and Failures

Shipping social publishing to production requires guarding against network interruptions and quota exhaustion.

Preventing Duplicate Posts With clientRequestId

Network connections drop. If a request times out while waiting for a server response, a naive client will retry the call. Without an idempotency guarantee, that retry creates a duplicate post on the user’s feed.

InvisibleAPI accepts an optional clientRequestId (up to 128 characters). If your application sends a request with a clientRequestId that matches a previously received job, the server returns the existing job record rather than dispatching a duplicate:

# Safe retry with identical clientRequestId
curl -X POST "https://api.invisibleapi.ai/api/v1/organizations/$ORG_ID/publishing/jobs" \
  -H "Authorization: Bearer $INVISIBLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": ["acct_ig_8f29c41a"],
    "caption": "Product launch details.",
    "mediaItems": [{"mediaType": "image", "sourceUrl": "https://cdn.example.com/p1.jpg"}],
    "clientRequestId": "product-launch-v1"
  }'

If the payload differs for an existing key, the API returns a 409 publishing.idempotency_mismatch error, alerting you to a payload discrepancy.

Platform Rate Limits and Quotas

Meta enforces a strict rate limit on publishing: 100 API-published posts within a 24-hour moving window per Instagram account.

If your application exceeds this threshold, Meta returns code 9 with subcode 2207042, and its guidance is to retry the following day. Track your dispatch volume locally to schedule posts across multiple days if you manage high-frequency publishing workflows.

Failure Classification: Caller Errors vs Platform Outages

When a post fails, knowing who owns the fix determines how your system should react:

If you build retries yourself, our guide to Instagram API error codes sorts every documented code into retry, fix, escalate or stop. To run publishing on a schedule from your own code, see how to automate Instagram posts with a queue and a failure policy.

For more details on platform thresholds, review how Instagram account limits and constraints work, or see how a social media publishing limits guardrail manages backoff.

Next Steps for Your Build

Publishing to Instagram programmatically comes down to managing the media container lifecycle. If you are building a custom integration for a single account, Meta’s Graph API endpoints provide full direct control over the three container stages.

If you are building an application, internal tool, or AI agent that publishes across multiple accounts, managing tokens, child containers, and error classification in your own codebase quickly becomes cumbersome, which is the work the Instagram posting API takes off your plate. Next, read our guide on how to connect an Instagram account, copy our social media publishing calendar template.

Post to X and Instagram from one API.

Connect the accounts once, create a publishing job, and poll it until it is published. Preflight validation catches a broken post before it reaches the platform, and a clientRequestId makes retries safe.

Sources

Frequently asked questions

What is the Instagram API?
The Instagram API is Meta's official interface for software that works with Instagram professional accounts. It publishes images, carousels and Reels, reads insights and manages comments. It is part of Meta's Graph API, and personal accounts cannot use it.
Do you need a Facebook Page to use the Instagram API?
Only on the Facebook Login path. Meta offers two ways in: the Instagram API with Instagram Login, which needs no Facebook Page, and the Instagram API with Facebook Login, which needs the professional account linked to a Facebook Page.
Is the Instagram API free?
Meta publishes no usage fee for the Instagram API. The real costs are the setup and review work, hosting your media on public URLs, and the engineering time to handle tokens, containers, limits and failures.
Can you upload image or video files directly to the Instagram API?
No. Meta's Instagram Content Publishing API does not accept direct multipart/form-data binary uploads when creating a post. Your media must be hosted on a publicly accessible HTTPS URL with valid MIME headers so Meta's servers can download the asset directly.
Why does Meta use a 3-step container flow to publish on Instagram?
Instagram processes, resizes, and encodes media before publishing. Because video encoding and image transcoding take time, Meta separates the process into three steps: creating a media container, polling its status until finished, and then publishing the container.
Do videos uploaded through the Instagram API publish as regular posts or Reels?
Every single video published through Meta's Content Publishing API is published automatically as an Instagram Reel. Standard feed videos were deprecated by Instagram in favor of the Reels format.
What is the daily posting limit for the Instagram API?
Meta enforces a quota of 100 API-published posts per Instagram Professional account within a 24-hour moving window. A carousel post containing multiple images or videos counts as a single post toward this 100-post limit.
Tags: #Publishing API

Ready to get started with InvisibleAPI?

Start building with InvisibleAPI today.

Related posts

View all posts

Best Social Media APIs for Developers in 2026

The best social media APIs for developers in 2026, grouped by job: official platform APIs, unified publishing APIs, reviews and UGC, local listings, and social data.

Instagram New Features and Updates: October 2026

Every Instagram update from July to October 2026 in one list, with what changed, who gets it, and whether you can use it through the Instagram API. Updated monthly.

Ayrshare Alternatives: 5 Publishing APIs Compared

Ayrshare Alternatives: 5 Publishing APIs Compared

Compare the top Ayrshare alternatives for developers in 2026. A breakdown of pricing cliffs, multi-tenancy, MCP agent support, and headless posting reliability.