Content & Marketing

Schedule Instagram Posts Using Python and AI

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.

  1. Install dependencies:
pip install requests openai python-dotenv apscheduler
  1. Generate a long-lived access token via the Meta Graph API Explorer.
  2. 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_time must be a future Unix timestamp within 1–75 days. Verify timezone offsets and integer casting.
  • Container Polling Timeouts: If status_code remains IN_PROGRESS after 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.