Schedule Instagram Posts Using Python and AI
This guide provides a production-ready Python script to automate Instagram publishing. By integrating AI-driven copy generation with Meta’s official endpoints, teams can streamline their AI Content Creation & Marketing Automation pipelines without third-party SaaS overhead.
1. Environment & Meta API Configuration
Prerequisites: Python 3.9+, an Instagram Business/Creator account, and a Meta Developer App with instagram_basic and instagram_content_publish permissions.
- Install dependencies:
pip install requests openai python-dotenv apscheduler
- Generate a long-lived access token via the Meta Graph API Explorer.
- Store credentials securely in
.env:
IG_USER_ID=your_ig_business_account_id
IG_ACCESS_TOKEN=your_long_lived_token
OPENAI_API_KEY=sk-...
2. AI Caption Generation Module
Use OpenAI to generate platform-optimized captions. The prompt enforces JSON output for reliable parsing.
import os
import json
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def generate_caption(topic: str) -> dict:
prompt = f"""Generate an Instagram caption for: '{topic}'.
Return ONLY valid JSON with keys: 'caption' (string, max 2200 chars) and 'hashtags' (list of 5-8 strings)."""
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}]
)
return json.loads(response.choices[0].message.content)
3. Core Publishing & Scheduling Script
Meta requires a two-step process: create a media container, then publish/schedule it. Implement container polling to verify readiness before scheduling.
import time
import requests
IG_USER_ID = os.getenv("IG_USER_ID")
TOKEN = os.getenv("IG_ACCESS_TOKEN")
BASE_URL = f"https://graph.facebook.com/v18.0/{IG_USER_ID}"
def schedule_post(img_url: str, caption: str, hours_from_now: int = 1):
# Step 1: Create Media Container
create_res = requests.post(
f'{BASE_URL}/media',
params={'image_url': img_url, 'caption': caption, 'access_token': TOKEN}
)
create_res.raise_for_status()
media_id = create_res.json()['id']
# Step 2: Poll until container is ready
for _ in range(10):
status = requests.get(f'{BASE_URL}/{media_id}', params={'fields': 'status_code', 'access_token': TOKEN}).json()
if status.get('status_code') == 'FINISHED':
break
time.sleep(3)
# Step 3: Schedule Publication
schedule_time = int(time.time()) + (hours_from_now * 3600)
publish_res = requests.post(
f'{BASE_URL}/media_publish',
params={'creation_id': media_id, 'scheduled_publish_time': schedule_time, 'access_token': TOKEN}
)
publish_res.raise_for_status()
return publish_res.json()
The scheduling logic relies on precise Unix timestamp conversion to comply with Meta’s 1–75 day window. Implementing this correctly is foundational for reliable Automated Social Media Posting at scale.
4. Execution & Cron Deployment
Wrap the workflow in APScheduler for persistent execution. Add retry logic for transient API failures.
from apscheduler.schedulers.blocking import BlockingScheduler
def run_scheduler():
try:
topic = "New Python AI automation tutorial"
content = generate_caption(topic)
full_caption = f"{content['caption']}\n\n{' '.join(content['hashtags'])}"
schedule_post("https://your-cdn.com/image.jpg", full_caption, hours_from_now=24)
print("Post scheduled successfully.")
except Exception as e:
print(f"Scheduling failed: {e}")
scheduler = BlockingScheduler()
scheduler.add_job(run_scheduler, 'cron', hour=9, minute=0)
scheduler.start()
5. Troubleshooting & Error Resolution
- OAuthException (#190): Token expired or revoked. Regenerate via Meta Graph API Explorer and update
.env. - Invalid parameter (#100):
scheduled_publish_timemust be a future Unix timestamp within 1–75 days. Verify timezone offsets and integer casting. - Container Polling Timeouts: If
status_coderemainsIN_PROGRESSafter 30s, the image URL may be inaccessible to Meta’s crawler. Ensure public HTTPS access and valid MIME types. - Rate Limits (429/500): Implement exponential backoff.
if response.status_code == 400: handle_oauth_error()
Monitor API response headers for x-business-use-case-usage to adjust request frequency.