Instagram Graph API: Publish a Photo
The exact two-step request flow to publish a single photo to Instagram using the Graph API.
Natively, publishing a photo via the Instagram Graph API is two calls: POST /{ig-user-id}/media with image_url pointing at a publicly fetchable HTTPS asset to create a container, then — after polling status_code until FINISHED — POST /{ig-user-id}/media_publish with the container’s ID as creation_id. The container expires roughly 24 hours after creation if it is never published.
This page is specifically about the native Instagram Graph API call shape for a single photo — the two-step container-then-publish flow Meta's own documentation describes, independent of any wrapper layer. If you want the PostGate-simplified one-request version instead, see the single-request page.
Step 1 — Create the media container
Natively, this is POST /{ig-user-id}/media with image_url pointed at a publicly fetchable HTTPS asset and an optional caption. Instagram fetches the asset itself and returns a container ID immediately — the container is not published yet, and the asset is not yet validated.
POST /{ig-user-id}/media
?image_url=https://cdn.example.com/product.jpg
&caption=New drop, live now.
&access_token={access-token}Step 2 — Poll the container until it finishes
Read GET /{ig-container-id}?fields=status_code and wait for FINISHED. Meta's own guidance recommends polling about once per minute for no more than five minutes for a typical image; longer waits are unusual for a single photo specifically, unlike video or Reels containers, which routinely take longer — see telling a slow container from a genuinely stuck one if a photo container is not resolving.
Step 3 — Publish the container
Once status_code reads FINISHED, call POST /{ig-user-id}/media_publish with the container's ID as creation_id. The response is the published media object's ID — the container itself expires roughly 24 hours after creation if it is never published, so this step needs to happen inside that window.
POST /{ig-user-id}/media_publish
?creation_id={ig-container-id}
&access_token={access-token}What does a single PostGate request replace?
POST /api/posts performs both native calls for you, waits out the polling loop internally, and reports the result as a single target reaching published — see the posts API and the delivery lifecycle. You still get the same underlying media ID and permalink Instagram returns from media_publish; you just do not have to manage the container-polling loop, token refresh, or retry logic yourself.
Frequently asked questions
How often should I poll an Instagram photo container?
Meta’s own guidance recommends polling GET /{ig-container-id}?fields=status_code about once per minute for no more than five minutes for a typical image. Longer waits are unusual for a single photo specifically — unlike video or Reels containers, which routinely take longer.
How long does an unpublished Instagram container last?
Roughly 24 hours after creation. If the container is never published inside that window it expires, so the media_publish call has to happen before then.
Does creating an Instagram container mean the photo is validated?
No. POST /{ig-user-id}/media returns a container ID immediately, but the container is not published yet and the asset is not yet validated — Instagram fetches the asset itself and reports the outcome through the container’s status_code.
What does PostGate replace in the native photo publish flow?
POST /api/posts performs both native calls for you, waits out the polling loop internally, and reports the result as a single target reaching published. You still get the same underlying media ID and permalink Instagram returns from media_publish — you just do not have to manage the container-polling loop, token refresh, or retry logic yourself.
