Instagram API - Post to Instagram via REST API
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_basicplusinstagram_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 samepostGroupId.
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
scheduledTimea few seconds in the future — a time already in the past is clamped to server time and flagged with aSCHEDULED_TIME_COERCEDwarning (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_basicplusinstagram_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.coverUrlto 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.