Instagram Reel container status_code: wait for FINISHED, not 200
An Instagram Reel container is not publishable until status_code is FINISHED, and it EXPIRES after 24 hours. A polling loop and the 100-post daily limit.

After you create a Reel container, Instagram processes the video asynchronously. You read GET /IG_CONTAINER_ID?fields=status_code and publish only when it returns FINISHED. An unpublished container goes EXPIRED after 24 hours, and a failed run reports ERROR. The same page limits an account to 100 API-published posts within a 24-hour moving period, a limit it ties to the media_publish endpoint, and it suggests checking a container once per minute for no more than five minutes.
Status values on the page
| `status_code` | Meaning on the page |
|---|---|
IN_PROGRESS | Still processing |
FINISHED | Ready for publication |
PUBLISHED | Media successfully published |
ERROR | Publishing process failed |
EXPIRED | Container not published within 24 hours |
A polling loop
Poll with a delay, stop on any terminal value and give up before the 24-hour expiry. The get_status function is yours: it calls the Graph endpoint with your token.
import time
def wait_finished(get_status, every=60, limit=300):
waited = 0
while waited < limit:
code = get_status()
if code in ("FINISHED", "PUBLISHED"):
return code
if code in ("ERROR", "EXPIRED"):
raise RuntimeError("container " + code)
time.sleep(every)
waited += every
raise TimeoutError("still IN_PROGRESS after %d s" % limit)
print(wait_finished(lambda: "FINISHED"))
Avoid creating a second container
If the loop times out, check the status once more later rather than creating a new container for the same file. The 100-post limit is tied to publishing, so the cost of a duplicate container is wasted processing and a second file to track, not quota. With Sume, keep the finished video URL from the job result and reuse it: video trim returns a durable MP4 URL, and a job can be read again by id without paying twice.
Order of operations
Create the container, store its id, poll until it is FINISHED, then publish and record the returned media id. Write each step to your log with the container id so a stuck item can be found by hand. If you must retry after ERROR, change the cause first (length, aspect, codec) and then create a new container; repeating the same file usually repeats the failure. Check the video before step one with a probe: width, height, duration and fps are all in the response of video inspect.
Trial Reels
The same page documents trial_params on Reels with one field, graduation_strategy, set to MANUAL (graduate in the app) or SS_PERFORMANCE (automatic graduation if performance is strong). See Trial Reels and graduation strategy.
Limits
The loop follows the page's suggestion of one check per minute for up to five minutes; lengthen it only if your own tests show Reels need longer. The page does not say whether Reels have their own daily cap beyond the shared 100 posts, so treat 100 as the ceiling for all API-published posts.
Sources
Related posts
More in Developers
- Clickable transcript from Sume STT word timestamps in Python
Turn a Sume speech-to-text result into HTML where each word seeks the audio player to its start time. Runnable Python, with the result envelope handled safely.
- Sume job error category quota or queue vs 402 and 429
A Sume job error category quota means add funds or lower cost; queue means retry with the same key. They sit on the job, apart from 402 and 429 at submit.
- Sume job failed with worker_timeout: poll again or retry?
A Sume job error in the worker_timeout or generation_timeout category means poll status or retry later. runtime_unavailable means retry later, gently.
- Sume job metadata on Kling motion control and H3 Max lip sync
Both Kling 3.0 Motion Control and MiniMax H3 Max Lip Sync accept a metadata object stored with the Sume job request. It is not sent to the provider.
Written by Sume