TL;DR, Quick Answer
9 min readAdding upload_type=resumable to POST /<IG_ID>/media returns an id and a uri on rupload.facebook.com instead of fetching your video by URL. The upload itself is a POST to that host with Authorization OAuth, offset and file_size headers, or a file_url header for a hosted file. Meta restricts the flow to apps using Facebook Login for Business, publishes no size threshold that makes it mandatory, and documents no way to resume a broken transfer.
What is an Instagram resumable upload?
Meta calls it an Instagram resumable upload, and it is a two host flow: POST /<IG_ID>/media with upload_type=resumable creates the container on graph.facebook.com and hands back an upload target, then the bytes go to rupload.facebook.com in a second request that you control.
The default is the opposite arrangement. In a standard upload you pass video_url and Meta fetches the file from you: "We will cURL your image using the passed in URL so it must be on a public server." Your server answers a request from Meta's crawler, and the transfer succeeds or fails somewhere you cannot see. Resumable inverts that. You open the connection, you send the bytes, and you get a response about them.
Meta's stated reason for the flow appears on the Content Publishing guide's endpoint list, typo and all: "upload_type=resumable Create a resumbable upload session to upload large videos from an area with frequent network interruptions or other transmission failures."
The parameter itself is documented as optional and case sensitive on the media reference: "An optional parameter for users want to upload video through the rupload protocol, values can be set to lowercase string value: resumable." Lowercase matters. RESUMABLE is not a documented value.
- You pass a video_url and wait for Meta to fetch it
- Your server answers a request from Meta's crawler
- The transfer succeeds or fails somewhere you cannot see
- You open the connection to rupload.facebook.com yourself
- You send the bytes and set the offset and file_size headers
- You get the response about the upload directly
What does each step of the flow send?
Four steps, two hosts, and a different authorization scheme on each one.
| Step | Host | Request |
|---|---|---|
| 1. Open the session | graph.facebook.com | POST /<IG_USER_ID>/media with media_type, upload_type=resumable, access_token |
| 2. Send the bytes | rupload.facebook.com | POST /ig-api-upload/<API_VERSION>/<IG_CONTAINER_ID> with Authorization, offset, file_size |
| 3. Check the container | graph.facebook.com | GET /<IG_CONTAINER_ID>?fields=status_code |
| 4. Publish | graph.facebook.com | POST /<IG_ID>/media_publish with creation_id |
Step one differs from a standard container creation in what it leaves out. There is no video_url, because there is nothing for Meta to fetch yet. A reel session is the full shape:
POST https://graph.facebook.com/v25.0/<YOUR_APP_USERS_INSTAGRAM_USER_ID>/media
?media_type=REELS
&upload_type=resumable
&caption=<IMAGE_CAPTION>
&collaborators=<COLLABORATOR_USERNAMES>
&cover_url=<COVER_URL>
&audio_name=<AUDIO_NAME>
&location_id=<LOCATION_PAGE_ID>
&thumb_offset=<THUMB_OFFSET>
&access_token=<USER_ACCESS_TOKEN>
The response carries a second field that a standard container does not return:
{
"id": "<IG_CONTAINER_ID>",
"uri": "https://rupload.facebook.com/ig-api-upload/v25.0/<IG_CONTAINER_ID>"
}Use the uri Meta hands you rather than assembling the path yourself. Step two then posts the file to it:
curl -X POST "https://rupload.facebook.com/ig-api-upload/v25.0/<IG_CONTAINER_ID>" \
-H "Authorization: OAuth <USER_ACCESS_TOKEN>" \
-H "offset: 0" \
-H "file_size: Your_file_size_in_bytes" \
--data-binary "@Your_local_file_path.extension"Meta documents the two numeric headers in one line each. "offset is set to the first byte being upload, generally 0." "file_size is set to the size of your file in bytes." A hosted file skips the body entirely and moves the source into a third header instead:
curl -X POST "https://rupload.facebook.com/ig-api-upload/v25.0/<IG_CONTAINER_ID>" \
-H "Authorization: OAuth <USER_ACCESS_TOKEN>" \
-H "file_url: <VIDEO_URL>"Success is two fields: {"success":true,"message":"Upload successful."}.
Why is the authorization header different on rupload?
Because Meta writes it differently on each host, and the examples are side by side on the same page. Calls to graph.facebook.com in the Content Publishing guide use -H "Authorization: Bearer <ACCESS_TOKEN>". Every call to rupload.facebook.com on that same page uses -H "Authorization: OAuth <ACCESS_TOKEN>".
Same token, different scheme name. Meta gives no explanation, and the rupload examples never show Bearer. Copy the scheme from the example that matches the host you are calling.
Two more details are easy to lose in the code blocks. Meta's published curl for the rupload step contains a stray backtick inside the URL, sitting between the container ID placeholder and the closing quote. It is a typo in the doc, not a piece of syntax. And the Content Publishing guide's own parameter list for the upload step stops mid sentence: it announces "the following parameters", prints access_token, then ends on an empty bullet. The complete header list only exists on the media endpoint reference, not in the guide.
When is resumable required rather than optional?
Meta never publishes a file size that makes it mandatory, and the honest answer is that the only hard requirement is about your login flow, not your file.
The Content Publishing guide restricts the whole flow in one clause: upload_type=resumable is "Only for apps that have implemented Facebook Login for Business." The requirements table on the same page backs that up by listing host URLs per login type. Instagram API with Instagram Login gets graph.instagram.com. Instagram API with Facebook Login gets graph.facebook.com and rupload.facebook.com, annotated "(For resumable video uploads)".
AdaptlyPost
Start 7-Day FREE Trial
All-platform analytics
Social Inbox
AI-powered assistant
So an app built on Business Login for Instagram cannot use resumable at all. Video has to arrive through video_url and Meta's fetch. That is a login architecture decision made long before anyone uploads a file, and it is not reversible per request.
Where resumable is available, Meta's guidance is qualitative rather than numeric. The trigger is "large videos from an area with frequent network interruptions or other transmission failures", with no byte threshold attached. The only related numbers Meta does publish are the specification ceilings: reels cap at 300 MB and 15 minutes, stories at 100 MB and 60 seconds. Neither is described as a resumable threshold.
Practical reading: use resumable whenever your source file is local rather than already sitting on a CDN, because the alternative requires you to host the file publicly for the duration of Meta's fetch. That matters most for anything long, which is where uploading long form video to Instagram gets awkward and where the reel length limits Meta has been testing push file sizes up.

How do you actually resume an interrupted upload?
Meta does not document it. That is the largest gap in the feature, and it sits directly under the feature's name.
The offset header is the only mechanism that hints at partial transfers, and both places Meta describes it point at the same value: "offset is set to the first byte being upload, generally 0." There is no documented chunk size, no endpoint that reports how many bytes the server already holds, no second request shape for continuing a broken transfer, and no example anywhere in the Instagram Platform docs that passes a non zero offset. A "resumable" upload session, as published, is a single POST of the whole file with an offset field that is always zero in the examples.
What Meta does give you is a container that survives a failure long enough to retry from the beginning. Containers expire after 24 hours, and an account can create 400 of them in a rolling 24 hour period. A failed byte transfer costs one container out of that 400, not one publish out of your daily quota, so restarting is cheap in the budget that matters. Anything queued in advance still has to respect the same ceilings, which is why scheduled Instagram posts fail at the container stage more often than at the publish stage.
What does a failed upload look like?
A failure on the rupload host does not come back as a standard Graph API error object. It comes back as a debug_info envelope with the real error stringified inside it:
{
"debug_info": {
"retriable": false,
"type": "ProcessingFailedError",
"message": "{\"success\":false,\"error\":{\"message\":\"unauthorized user request\"}}"
}
}Parse retriable first. It is the field that tells you whether a retry is worth the container. false means the transfer will fail the same way again, and the example Meta chose, an unauthorized user request, is exactly that kind of failure.
Failures that surface later, on the Graph API side, use the normal code and subcode pairs.
| Symptom | Code | Subcode | Message |
|---|---|---|---|
| Upload failed for no stated reason | -1 | 2207053 | unknown upload error |
| Container expired before publish | -2 | 2207020 | The media you are trying to access has expired. Please try to upload again. |
| Container not found at publish | 24 | 2207008 | The media builder with creation id = {creation-id} does not exist or has been expired. |
| Published too early | 9007 | 2207027 | The media is not ready for publishing, please wait for a moment |
| Video format rejected | 352 | 2207026 | The video format is not supported. Please check spec for supported {video} format |
Meta scopes 2207053 to this flow specifically: "An unknown error occured during upload. Generate a new container and use it to try again. This should only affect video uploads."

Which errors show up after the upload succeeds?
The ones that come from processing, and you only see them by polling. Meta is explicit that a container ID proves nothing: "Video uploads are asynchronous, so receiving a container ID does not guarantee that the upload was successful."
GET /<IG_CONTAINER_ID>?fields=status_code returns one of five values.
status_code | Meaning Meta publishes |
|---|---|
IN_PROGRESS | The container is still in the publishing process |
FINISHED | The container and its media object are ready to be published |
ERROR | The container failed to complete the publishing process |
EXPIRED | The container was not published within 24 hours and has expired |
PUBLISHED | The container's media object has been published |
Only FINISHED is safe to publish. Requesting the status field alongside status_code is worth the extra parameter, because Meta defines it as the detail line: "If status_code is ERROR, this value will be an error subcode."
AdaptlyPost
Start 7-Day FREE Trial
All-platform analytics
Social Inbox
AI-powered assistant
Meta caps the polling advice at a specific cadence: "We recommend querying a container's status once per minute, for no more than 5 minutes." A long reel can still be processing after that, and Meta does not say what to do next. The workable pattern is to keep polling at a slower interval until the container either finishes or hits the 24 hour expiry, which is also how scheduling reels through the API has to be built and how story containers behave on their own 24 hour clock.
Frequently asked questions
What does upload_type=resumable do on the Instagram API?
It creates a container that expects you to push the video to rupload.facebook.com yourself, instead of having Meta fetch it from a video_url you host. The response includes both an id and a uri pointing at the upload host.
Which host handles Instagram resumable uploads?
rupload.facebook.com, at the path /ig-api-upload/<API_VERSION>/<IG_CONTAINER_ID>. Container creation, status checks and publishing all stay on graph.facebook.com.
Which headers does the rupload request need?
Authorization: OAuth <ACCESS_TOKEN> plus either offset and file_size for a local file, or file_url for a file already hosted publicly. Note that the rupload examples use OAuth where the Graph API examples use Bearer.
Can apps using Instagram Login use resumable uploads?
No. Meta restricts upload_type=resumable to apps that have implemented Facebook Login for Business, and lists rupload.facebook.com only under that login type.
At what file size does Instagram require a resumable upload?
Meta publishes no such threshold. The guide recommends resumable for "large videos from an area with frequent network interruptions" without naming a size, and documents the parameter itself as optional.
How do you resume a broken Instagram upload?
Meta does not document a resume procedure. The offset header exists but every published example sets it to 0, and no endpoint reports how many bytes the server already received. Generate a new container and start again.
How long does an Instagram upload container stay valid?
A container expires 24 hours after you create it, whether you published it or not. Miss that window and you get code -2, subcode 2207020, with the message that the media you are trying to access has expired and you need to try uploading again. The status_code field reports EXPIRED for the same reason.
How often should you poll an Instagram container's status?
Meta caps its own advice at once per minute for no more than five minutes. A long reel can still show IN_PROGRESS after that window, and Meta does not document what to do next. The practical answer is to keep polling at a slower interval until the container either reaches FINISHED or hits its 24 hour expiry.
What does the retriable field in a rupload error mean?
It is the first field to check in the debug_info envelope rupload returns on failure. A value of false means the transfer will fail the same way again if you resend it, exactly as in Meta's own example of an unauthorized user request. Checking retriable before you retry saves you from spending another container on a request that cannot succeed.
Does a container ID mean the Instagram upload succeeded?
A container ID alone proves nothing. Meta states plainly that video uploads are asynchronous, so receiving a container ID does not guarantee the upload was successful. The only way to know is polling the container with GET /<IG_CONTAINER_ID>?fields=status_code and waiting for FINISHED before you publish.
Put this into practice with AdaptlyPost
Was This Article Helpful?
Let us know what you think!
See us more often in Google
One click marks AdaptlyPost as a preferred source, so our articles sit higher in your Top Stories, AI Mode, and AI Overviews.
Before you go...
AdaptlyPost
Schedule your content across all platforms
Manage all your social media accounts in one place with AdaptlyPost.
All-platform analytics
Social Inbox
AI-powered assistant
Related Glossary Terms


What the instagram_business_content_publish Scope Actually Grants
The instagram_business_content_publish scope lets an app create organic Instagram posts, and it depends on instagram_business_basic on every call.


Every Limit the Instagram Reels API Puts on Your Video
The Instagram Reels API caps a reel at 15 minutes and 300 MB and rejects anything but MOV or MP4. Every documented spec, plus the error each violation returns.


What Instagram's content_publishing_limit Endpoint Returns
Instagram's content_publishing_limit endpoint returns quota_usage plus a config block holding quota_total 50 and quota_duration 86400 seconds.
Related Articles


Meta Sets the Instagram API alt_text Character Limit at 1,000
Meta caps the Instagram API alt_text character limit at 1,000 and scopes the field to still images. Reels and stories accept no alt text at all.


Why a LinkedIn Access Token Expires After 60 Days
Every LinkedIn access token runs 60 days and expires_in returns 5184000. Refresh token rules, what kills a token early, and how Meta's 60 days differ.


Where the LinkedIn Alt Text Character Limit Is Actually Written
The LinkedIn alt text character limit is 4,086 characters on the API altText field, and LinkedIn publishes no limit at all for the alt text box in the app.

