Instagram API - Post to Instagram via REST API

View as Markdown

Post to Instagram programmatically using the Publora REST API. A simpler alternative to the Instagram Graph API, Instagram Basic Display API, or Instagrapi.

Instagram API Overview

Publora connects Instagram accounts through Instagram Login for Business and requests instagram_business_basic plus instagram_business_content_publish; personal accounts are unsupported. Whether Meta accepts a particular Creator account is determined by Meta, not Publora's code.

Why Use Publora Instead of Instagram Graph API / Instagrapi?

Feature Publora API Instagram Graph API
Authentication Single API key Instagram Business Login (OAuth 2.0)
API access Instant Facebook app review
Reels & Stories Supported Supported
Multi-platform Post to 10 platforms Instagram only
Setup time 5 minutes Days to weeks
Carousel support Yes Yes

Keywords: Instagram API, Instagram posting API, Instagram Graph API, post to Instagram programmatically, Instagram REST API, Instagram developer API, Instagram automation API, Instagram Reels API, Instagram Stories API, Instagram content API, Instagrapi alternative, Instagram bot API

Platform ID Format

instagram-{accountId}

Where {accountId} is your Instagram Business account ID assigned during connection via Instagram OAuth.

Requirements

  • A professional Instagram account accepted by Meta's Instagram Login for Business flow
  • Connected via Instagram OAuth through the Publora dashboard
  • API key from Publora

Publora connects Instagram accounts through Instagram Login for Business and requests instagram_business_basic plus instagram_business_content_publish; personal accounts are unsupported. Whether Meta accepts a particular Creator account is determined by Meta, not Publora's code.

API Limits

These are critical limits specific to the Instagram Graph API (different from native app limits):

Character Limit

  • Caption: 2,200 characters (first 125 characters visible before "more")

Image Limits (API)

Limit Value
Max file size 8 MB
Max count (carousel) 10 images (native app allows 20)
Formats JPEG, PNG, WebP (WebP is auto-converted to JPEG before publishing)

Warning: You cannot mix images and videos in the same carousel via the API.

Video Limits (API)

Limit Reels Carousel Videos
Max duration 15 minutes (900 seconds) for Reels; feed video supports up to 60 minutes (3600 seconds). 60 seconds
Min duration 3 seconds 3 seconds
Max file size 300 MB 300 MB
Formats MP4, MOV MP4, MOV

Rate Limits

Instagram-side posting quotas are account-dependent, may change without notice, and are not a Publora numeric contract.

Important API Restrictions

The following features are not available via the Instagram Graph API:

  • Personal accounts are unsupported; account eligibility is determined by Meta's Instagram Login for Business flow
  • Shopping tags
  • Branded content tags
  • Filters and effects
  • Music/audio additions
  • Mixing images and videos in carousels

Supported Content

Type Supported Limits
Text only No Instagram requires at least one image or video
Images Yes JPEG, PNG, WebP (WebP auto-converted to JPEG), max 8 MB, 10 per carousel
Videos (Reels) Yes MP4/MOV, max 15 minutes (900 seconds), max 300 MB
Videos (Stories) Yes MP4/MOV, requires videoType: "STORIES" setting
Carousels Yes 2-10 items (API limit; native app allows 20)

Platform-Specific Settings

Instagram supports a platformSettings object to control video behavior:

{
  "platformSettings": {
    "instagram": {
      "videoType": "REELS",
      "coverUrl": "https://cdn.example.com/covers/reel-cover.jpg"
    }
  }
}
Setting Values Default Description
videoType "REELS", "STORIES" "REELS" Determines how videos are published
coverUrl http(s) URL string Custom cover image for Reels (alias: cover_url). Must resolve to a JPEG image. Two ways to set it: (1) upload a file (JPEG/PNG/WebP, up to 8 MB — Publora hosts it and converts to JPEG) and use the returned URL, or (2) pass your own publicly accessible JPEG URL, which Instagram fetches server-side when the Reel is created. When set, it takes precedence over frame-based cover selection (videoTimestamp). Send an empty string to clear. Non-JPEG or non-http(s) URLs are rejected with 400. Settable on both create-post and update-post. Ignored for Stories and images.
videoTimestamp number (milliseconds) Dashboard-only top-level field for frame selection; it is not accepted by the REST create/update contract. Sending thumbOffset under platformSettings.instagram returns 400 PLATFORM_SETTING_UNKNOWN. API callers should use coverUrl.

Examples

Post with an Image

JavaScript (fetch)

const response = await fetch('https://api.publora.com/api/v1/create-post', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-publora-key': 'YOUR_API_KEY'
  },
  body: JSON.stringify({
    content: 'Sunset views from the office rooftop. #startup #views',
    platforms: ['instagram-11223344']
  })
});
 
const data = await response.json();
console.log(data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }

Python (requests)

import requests
 
response = requests.post(
    'https://api.publora.com/api/v1/create-post',
    headers={
        'Content-Type': 'application/json',
        'x-publora-key': 'YOUR_API_KEY'
    },
    json={
        'content': 'Sunset views from the office rooftop. #startup #views',
        'platforms': ['instagram-11223344']
    }
)
 
data = response.json()
print(data)
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }

cURL

curl -X POST https://api.publora.com/api/v1/create-post \
  -H "Content-Type: application/json" \
  -H "x-publora-key: YOUR_API_KEY" \
  -d '{
    "content": "Sunset views from the office rooftop. #startup #views",
    "platforms": ["instagram-11223344"]
  }'
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }

Node.js (axios)

const axios = require('axios');
 
const response = await axios.post('https://api.publora.com/api/v1/create-post', {
  content: 'Sunset views from the office rooftop. #startup #views',
  platforms: ['instagram-11223344']
}, {
  headers: {
    'Content-Type': 'application/json',
    'x-publora-key': 'YOUR_API_KEY'
  }
});
 
console.log(response.data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }

Note: Instagram requires media on every post. First create the post, then upload media using the media upload workflow with the returned postGroupId. For carousels, upload multiple files to the same postGroupId.

Post a Carousel

Carousels require 2-10 images. The workflow is: create a draft post, upload images, then schedule.

JavaScript (fetch)

const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://api.publora.com/api/v1';
 
// Step 1: Create a draft post (no scheduledTime)
const postResponse = await fetch(`${BASE_URL}/create-post`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-publora-key': API_KEY
  },
  body: JSON.stringify({
    content: '10 tips for better code reviews. Swipe through! #coding #devtips',
    platforms: ['instagram-11223344']
    // No scheduledTime = draft
  })
});
 
const { postGroupId } = await postResponse.json();
 
// Step 2: Upload each image (2-10 images for Instagram carousels)
const images = ['tip1.jpg', 'tip2.jpg', 'tip3.jpg'];
 
for (const fileName of images) {
  // Get upload URL
  const uploadUrlResponse = await fetch(`${BASE_URL}/get-upload-url`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-publora-key': API_KEY
    },
    body: JSON.stringify({
      fileName,
      contentType: 'image/jpeg',
      type: 'image',
      postGroupId
    })
  });
 
  const { uploadUrl } = await uploadUrlResponse.json();
 
  // Upload to S3
  const fileBuffer = await fs.promises.readFile(`./${fileName}`);
  await fetch(uploadUrl, {
    method: 'PUT',
    headers: { 'Content-Type': 'image/jpeg' },
    body: fileBuffer
  });
}
 
// Step 3: Schedule the post
await fetch(`${BASE_URL}/update-post/${postGroupId}`, {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'x-publora-key': API_KEY
  },
  body: JSON.stringify({
    status: 'scheduled',
    scheduledTime: '2026-03-15T14:00:00.000Z'
  })
});
 
console.log('Carousel scheduled!');

Python (requests)

import requests
 
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://api.publora.com/api/v1'
HEADERS = {
    'Content-Type': 'application/json',
    'x-publora-key': API_KEY
}
 
# Step 1: Create a draft post (no scheduledTime)
post_response = requests.post(
    f'{BASE_URL}/create-post',
    headers=HEADERS,
    json={
        'content': '10 tips for better code reviews. Swipe through! #coding #devtips',
        'platforms': ['instagram-11223344']
        # No scheduledTime = draft
    }
)
 
post_group_id = post_response.json()['postGroupId']
 
# Step 2: Upload each image (2-10 images for Instagram carousels)
images = ['tip1.jpg', 'tip2.jpg', 'tip3.jpg']
 
for file_name in images:
    # Get upload URL
    upload_response = requests.post(
        f'{BASE_URL}/get-upload-url',
        headers=HEADERS,
        json={
            'fileName': file_name,
            'contentType': 'image/jpeg',
            'type': 'image',
            'postGroupId': post_group_id
        }
    )
 
    upload_url = upload_response.json()['uploadUrl']
 
    # Upload to S3
    with open(f'./{file_name}', 'rb') as f:
        requests.put(upload_url, headers={'Content-Type': 'image/jpeg'}, data=f.read())
 
# Step 3: Schedule the post
requests.put(
    f'{BASE_URL}/update-post/{post_group_id}',
    headers=HEADERS,
    json={
        'status': 'scheduled',
        'scheduledTime': '2026-03-15T14:00:00.000Z'
    }
)
 
print('Carousel scheduled!')

cURL

API_KEY="YOUR_API_KEY"
 
# Step 1: Create a draft post (no scheduledTime)
POST_RESPONSE=$(curl -s -X POST https://api.publora.com/api/v1/create-post \
  -H "Content-Type: application/json" \
  -H "x-publora-key: $API_KEY" \
  -d '{
    "content": "10 tips for better code reviews. Swipe through! #coding #devtips",
    "platforms": ["instagram-11223344"]
  }')
 
POST_GROUP_ID=$(echo "$POST_RESPONSE" | jq -r '.postGroupId')
 
# Step 2: Upload each image (2-10 images for Instagram carousels)
for FILE in tip1.jpg tip2.jpg tip3.jpg; do
  UPLOAD_RESPONSE=$(curl -s -X POST https://api.publora.com/api/v1/get-upload-url \
    -H "Content-Type: application/json" \
    -H "x-publora-key: $API_KEY" \
    -d "{
      \"fileName\": \"$FILE\",
      \"contentType\": \"image/jpeg\",
      \"type\": \"image\",
      \"postGroupId\": \"$POST_GROUP_ID\"
    }")
 
  UPLOAD_URL=$(echo "$UPLOAD_RESPONSE" | jq -r '.uploadUrl')
 
  curl -s -X PUT "$UPLOAD_URL" \
    -H "Content-Type: image/jpeg" \
    --data-binary @"./$FILE"
done
 
# Step 3: Schedule the post
curl -X PUT "https://api.publora.com/api/v1/update-post/$POST_GROUP_ID" \
  -H "Content-Type: application/json" \
  -H "x-publora-key: $API_KEY" \
  -d '{
    "status": "scheduled",
    "scheduledTime": "2026-03-15T14:00:00.000Z"
  }'

Note: For immediate publishing, set scheduledTime a few seconds in the future — a time already in the past is clamped to server time and flagged with a SCHEDULED_TIME_COERCED warning (see past scheduled times). For more details, see the media upload workflow.

Post a Reel

JavaScript (fetch)

const response = await fetch('https://api.publora.com/api/v1/create-post', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-publora-key': 'YOUR_API_KEY'
  },
  body: JSON.stringify({
    content: 'Behind the scenes of building our product. #buildinpublic',
    platforms: ['instagram-11223344'],
    platformSettings: {
      instagram: {
        videoType: 'REELS'
      }
    }
  })
});
 
const data = await response.json();
console.log(data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }

Python (requests)

import requests
 
response = requests.post(
    'https://api.publora.com/api/v1/create-post',
    headers={
        'Content-Type': 'application/json',
        'x-publora-key': 'YOUR_API_KEY'
    },
    json={
        'content': 'Behind the scenes of building our product. #buildinpublic',
        'platforms': ['instagram-11223344'],
        'platformSettings': {
            'instagram': {
                'videoType': 'REELS'
            }
        }
    }
)
 
data = response.json()
print(data)
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }

cURL

curl -X POST https://api.publora.com/api/v1/create-post \
  -H "Content-Type: application/json" \
  -H "x-publora-key: YOUR_API_KEY" \
  -d '{
    "content": "Behind the scenes of building our product. #buildinpublic",
    "platforms": ["instagram-11223344"],
    "platformSettings": {
      "instagram": {
        "videoType": "REELS"
      }
    }
  }'
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }

Node.js (axios)

const axios = require('axios');
 
const response = await axios.post('https://api.publora.com/api/v1/create-post', {
  content: 'Behind the scenes of building our product. #buildinpublic',
  platforms: ['instagram-11223344'],
  platformSettings: {
    instagram: {
      videoType: 'REELS'
    }
  }
}, {
  headers: {
    'Content-Type': 'application/json',
    'x-publora-key': 'YOUR_API_KEY'
  }
});
 
console.log(response.data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }

Platform Quirks

  • No text-only posts: Instagram requires at least one image or video. Attempting to post text without media will return an error.
  • Publora connects Instagram accounts through Instagram Login for Business and requests instagram_business_basic plus instagram_business_content_publish; personal accounts are unsupported. Whether Meta accepts a particular Creator account is determined by Meta, not Publora's code.
  • Direct Instagram connection: Publora connects directly to Instagram via Instagram Business Login. No Facebook Page is required.
  • Carousel limits: Carousels require between 2 and 10 media items. A single image is posted as a regular photo post, not a carousel.
  • Reel is the default: When posting a video, Publora defaults to publishing it as a Reel. Set videoType: "STORIES" to post as a Story instead.
  • Custom Reels cover: Set platformSettings.instagram.coverUrl to a publicly accessible JPEG URL to control the Reel's cover/thumbnail. Instagram downloads the image itself, so private or expiring URLs will fail at publish time. See Platform-Specific Settings.
  • Stories disappear: Stories are ephemeral and will disappear after 24 hours. This is standard Instagram behavior.
  • Image aspect ratios: Instagram images must be between 4:5 and 1.91:1. Scheduling rejects files outside that range with MEDIA_ASPECT_RATIO_INVALID; Publora does not silently crop them.
  • Caption hashtags: Hashtags are included in the caption text. There is no separate hashtags field.

Character Limits

Element Limit
Caption 2,200 characters
Hashtags 30 per post
Carousel items 2-10 media items

Publora is built by Creative Content Crafts, Inc. Need AI-powered content creation for LinkedIn, Threads, and X? Try Co.Actor — the best AI service for authentic thought leadership at scale.