Anyone who regularly posts on Instagram knows the drill: open the app, upload a photo, write a caption, add hashtags, hit publish. But what if you could automate this entire process? With the Instagram Graph API and a Meta Business Suite account, you can create and even schedule posts fully automatically via a REST API.
In this article, I'll show you how to set everything up and share working code examples you can adapt for your own projects.
Prerequisites
Before you can get started, you need a few things:
- An Instagram Business or Creator Account
- A Facebook Page linked to that Instagram account
- A Meta Developer Account and an app in the Meta Developer Portal
- A valid Access Token with the required permissions
Setting Up the Meta App
Head over to the Meta for Developers portal and create a new app (type: Business). In the app dashboard, add the Instagram Graph API product. Then, under App Review, request the following permissions:
instagram_basicinstagram_content_publishpages_read_engagement
For testing purposes, you can use a short-lived token from the Graph API Explorer. For production, you should implement the OAuth flow to get a long-lived token.
Finding Your Instagram Business Account ID
Before publishing, you need your Instagram Business Account ID. You can retrieve it through the linked Facebook Page:
curl -X GET \
"https://graph.facebook.com/v21.0/me/accounts?access_token={ACCESS_TOKEN}"
This returns the pages you manage. Take the id of the relevant page and query the linked Instagram account:
curl -X GET \
"https://graph.facebook.com/v21.0/{PAGE_ID}?fields=instagram_business_account&access_token={ACCESS_TOKEN}"
The response contains the instagram_business_account.id — save it, you'll need it for every publish request.
Publishing a Post — Two-Step Process
The Instagram Graph API uses a two-step publishing flow:
- Create a media container — upload the image URL and caption
- Publish the container — actually post it to your feed
Step 1: Create the Media Container
curl -X POST \
"https://graph.facebook.com/v21.0/{IG_USER_ID}/media" \
-d "image_url=https://example.com/my-image.jpg" \
-d "caption=My automated post! 🚀 #automation #api" \
-d "access_token={ACCESS_TOKEN}"
The response returns a creation_id which represents your media container.
Step 2: Publish the Container
curl -X POST \
"https://graph.facebook.com/v21.0/{IG_USER_ID}/media_publish" \
-d "creation_id={CREATION_ID}" \
-d "access_token={ACCESS_TOKEN}"
That's it — your post is now live on Instagram!
Scheduling Posts for Later
Instead of publishing immediately, you can schedule posts for a future time. Simply add the published and scheduled_publish_time parameters when creating the media container:
curl -X POST \
"https://graph.facebook.com/v21.0/{IG_USER_ID}/media" \
-d "image_url=https://example.com/my-image.jpg" \
-d "caption=Scheduled post 📅 #automation" \
-d "published=false" \
-d "scheduled_publish_time={UNIX_TIMESTAMP}" \
-d "access_token={ACCESS_TOKEN}"
The scheduled_publish_time must be a Unix timestamp between 10 minutes and 75 days in the future. After creating the container, publish it the same way — Instagram will hold it until the scheduled time.
A Complete Example in Python
Here's a practical Python script that ties it all together:
import requests
import time
ACCESS_TOKEN = "your_access_token"
IG_USER_ID = "your_instagram_business_account_id"
BASE_URL = "https://graph.facebook.com/v21.0"
def create_post(image_url: str, caption: str, schedule_time: int = None):
# Step 1: Create media container
payload = {
"image_url": image_url,
"caption": caption,
"access_token": ACCESS_TOKEN,
}
if schedule_time:
payload["published"] = "false"
payload["scheduled_publish_time"] = schedule_time
resp = requests.post(f"{BASE_URL}/{IG_USER_ID}/media", data=payload)
resp.raise_for_status()
creation_id = resp.json()["id"]
print(f"Media container created: {creation_id}")
# Step 2: Publish the container
publish_resp = requests.post(
f"{BASE_URL}/{IG_USER_ID}/media_publish",
data={
"creation_id": creation_id,
"access_token": ACCESS_TOKEN,
},
)
publish_resp.raise_for_status()
post_id = publish_resp.json()["id"]
print(f"Post published: {post_id}")
return post_id
# Publish immediately
create_post(
image_url="https://example.com/photo.jpg",
caption="Hello from the API! 🤖 #automation #python",
)
# Schedule for 24 hours from now
create_post(
image_url="https://example.com/photo2.jpg",
caption="Scheduled post 📅 #automation",
schedule_time=int(time.time()) + 86400,
)
Posting Carousels (Multiple Images)
You can also create carousel posts with multiple images. The process is similar but requires creating individual media containers for each image first, then combining them:
def create_carousel(image_urls: list, caption: str):
children_ids = []
# Create a container for each image
for url in image_urls:
resp = requests.post(
f"{BASE_URL}/{IG_USER_ID}/media",
data={
"image_url": url,
"is_carousel_item": "true",
"access_token": ACCESS_TOKEN,
},
)
resp.raise_for_status()
children_ids.append(resp.json()["id"])
# Create the carousel container
resp = requests.post(
f"{BASE_URL}/{IG_USER_ID}/media",
data={
"media_type": "CAROUSEL",
"caption": caption,
"children": ",".join(children_ids),
"access_token": ACCESS_TOKEN,
},
)
resp.raise_for_status()
creation_id = resp.json()["id"]
# Publish
publish_resp = requests.post(
f"{BASE_URL}/{IG_USER_ID}/media_publish",
data={
"creation_id": creation_id,
"access_token": ACCESS_TOKEN,
},
)
publish_resp.raise_for_status()
return publish_resp.json()["id"]
Important Limitations
Keep these limits in mind when working with the Instagram Graph API:
- Rate Limits: You can publish up to 25 posts per 24 hours per Instagram account
- Image Requirements: Images must be publicly accessible via URL, JPEG format is recommended, minimum 320px, maximum 1440px width
- Caption Length: Maximum 2,200 characters
- Hashtags: Maximum 30 hashtags per post
- Scheduling Window: Between 10 minutes and 75 days in the future
- No Stories/Reels: The Content Publishing API currently only supports feed posts and carousels (Reels support is in beta)
Error Handling Best Practices
The API can return various errors. Here's how to handle them gracefully:
def safe_publish(image_url: str, caption: str):
try:
resp = requests.post(
f"{BASE_URL}/{IG_USER_ID}/media",
data={
"image_url": image_url,
"caption": caption,
"access_token": ACCESS_TOKEN,
},
)
if resp.status_code == 400:
error = resp.json().get("error", {})
code = error.get("code")
if code == 9:
print("Rate limit reached. Try again later.")
elif code == 36003:
print("Image URL not accessible. Check the URL.")
else:
print(f"API error: {error.get('message')}")
return None
resp.raise_for_status()
return resp.json()["id"]
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
Conclusion
The Instagram Graph API opens up powerful automation possibilities. Whether you're building a social media management tool, automating content distribution, or scheduling posts for optimal engagement times — the API gives you full programmatic control over your Instagram presence.
Combined with a CI/CD pipeline or a simple cron job, you can build a fully automated posting workflow that saves you time and ensures consistency.
If you want to dive deeper into working with REST APIs in general, check out this article:
Discover more articles
Instagram-Posts automatisiert erstellen
Mit der Instagram Graph API und Meta Business Suite kannst du Posts vollautomatisch erstellen und planen. So geht’s mit der REST-API.
GraphQL: The future of APIs 🌐
GraphQL vs. REST: Discover the advantages and disadvantages of the modern query language for APIs! 🌐
NestJS: Server framework on steroids 🎉
NestJS is the turbo framework for NodeJS devs! 🚀 With TypeScript, OOP & FP every line of code becomes a pleasure! 😎
Automate Home Assistant with WebHooks
Firing automations in Home Assistant through external systems 🚀 Let me show you how to do it!
Ghost: The better WordPress
Why you should switch from WordPress to Ghost, I explain in this article 🛠️