Route 1: Meta's Graph API, directly
What you need before the first call
- An Instagram professional account (Business or Creator). Personal accounts cannot publish through the API.
- A Meta developer app with the Instagram publishing permission (
instagram_content_publishor the Instagram Login equivalent) and, for live use, Meta's app review approval. - An access token for the account. Short-lived tokens last an hour; exchange for a long-lived token (60 days) and refresh before expiry.
- Publicly reachable media URLs. Instagram fetches the image or video from your URL. JPEG only for images (up to 8 MB), MP4 or MOV for video.
The two-step publish
Step one creates a media container from your URL and caption. Step two publishes the container. Videos need a short wait between the two while Instagram processes them.
# 1. create the container
curl -X POST "https://graph.facebook.com/v21.0/{ig-user-id}/media" \
-d "image_url=https://cdn.example.com/launch.jpg" \
-d "caption=Three things I wish someone told me at 21" \
-d "access_token=$TOKEN"
# → {"id":"17889455560051444"}
# 2. publish it
curl -X POST "https://graph.facebook.com/v21.0/{ig-user-id}/media_publish" \
-d "creation_id=17889455560051444" \
-d "access_token=$TOKEN"import requests, time
G = "https://graph.facebook.com/v21.0"
IG, TOKEN = "17841400000000000", "EAAB..."
def post_image(url, caption):
c = requests.post(f"{G}/{IG}/media", data={
"image_url": url, "caption": caption, "access_token": TOKEN}).json()
return requests.post(f"{G}/{IG}/media_publish", data={
"creation_id": c["id"], "access_token": TOKEN}).json()
def post_reel(url, caption):
c = requests.post(f"{G}/{IG}/media", data={
"media_type": "REELS", "video_url": url,
"caption": caption, "access_token": TOKEN}).json()
# poll until the container is FINISHED, then publish
while True:
s = requests.get(f"{G}/{c['id']}", params={
"fields": "status_code", "access_token": TOKEN}).json()
if s["status_code"] == "FINISHED": break
time.sleep(5)
return requests.post(f"{G}/{IG}/media_publish", data={
"creation_id": c["id"], "access_token": TOKEN}).json()const G = "https://graph.facebook.com/v21.0";
const form = (o) => new URLSearchParams(o);
async function postImage(imageUrl, caption) {
const c = await fetch(`${G}/${IG}/media`, { method: "POST",
body: form({ image_url: imageUrl, caption, access_token: TOKEN }) }).then(r => r.json());
return fetch(`${G}/${IG}/media_publish`, { method: "POST",
body: form({ creation_id: c.id, access_token: TOKEN }) }).then(r => r.json());
}Carousels
Create one container per item with is_carousel_item=true, then a parent container with media_type=CAROUSEL and children= the item IDs, then publish the parent. Two to ten items.
The constraints that bite
| Constraint | Detail |
|---|---|
| 50 posts per 24 hours | Per professional account, rolling window. The API returns an error beyond it. |
| Hosted media only | You need a public URL for every image and video. Local files must be uploaded somewhere first. |
| JPEG only for images | PNG and WebP are rejected. Aspect ratio between 4:5 and 1.91:1. |
| App review | Publishing permissions need Meta's review before they work for accounts other than your own test users. |
| Token lifecycle | Long-lived tokens expire after 60 days and must be refreshed. |
| Scheduling | The API has no schedule call. You run your own queue and call publish at the right time. |
| Comment-to-DM | Separate webhooks and the messaging API, with their own permissions and review. |
Route 2: one command, then a sentence
If the goal is “my script or my AI posts to Instagram” rather than “I maintain a Meta integration”, the second route is a single install:
npx scorper@latest init
Sign in with an email code, say “connect my instagram”, approve in the browser. From then on:
Scorper holds the Meta app, the review, the tokens, the media hosting and transcoding, the scheduling queue and the comment webhooks. Local files are fine. The agent reads the post back and publishes when you confirm. For a cron job, Claude Code's claude -p "..." runs the same prompt unattended.
| Graph API yourself | Scorper | |
|---|---|---|
| Setup | Developer app, app review, token handling, media hosting | One command, one browser approval |
| Local files | No, public URL required | Yes |
| Scheduling | Build your own queue | Included, fires server-side |
| Other platforms | One integration each (LinkedIn, TikTok, X, YouTube) | Same connection |
| Comment-to-DM | Separate messaging API and review | Included |
| Cost | Free API, your engineering time | From $25 a month, 7-day free trial |
Frequently asked questions
- Can I post to Instagram from a Python script?
- Yes, through Meta's Graph API: create a media container from a publicly hosted image or video URL, then publish the container. You need a Meta developer app with the publishing permissions approved, an Instagram professional account, and a long-lived access token. Or skip all of that and let an AI agent post through Scorper with one install command.
- Can I post to Instagram without a Meta developer app?
- Not directly against the API: every Graph API call needs an app ID and token. Tools like Scorper hold the app for you, so you only approve the connection in your browser.
- Why does the API need a public image URL?
- Instagram's servers fetch the media from the URL you give when creating the container. It cannot accept a file upload in the same request, so you need hosting (S3, a CDN, your own server) for every image.
- What is the Instagram API posting limit?
- 25 API-published posts per professional account per rolling 24 hours. Carousels count as one post.
- Does instagrapi or another unofficial library work?
- Libraries that drive the private mobile API break often and put the account at risk of action from Meta. For anything you care about, use the official Graph API or a tool built on it.
- Can an AI agent post to Instagram programmatically?
- Yes. With Scorper, Claude Code, Codex, Cursor or the Claude app get a posting tool: one install, one browser approval, then "post launch.mp4 to instagram". The agent reads the post back before publishing.
Skip the Meta app. Post from your AI.
One line, an email code, one browser approval.
npx scorper@latest initStart free for 7 days 7 days free on every plan. The AI-agent connection is on Max ($25/month) and Studio.