Drawing 1 · Interactive
Upload the bytes, get a file handle back
https://graph.facebook.com/v26.0/upload:MTphdHRhY2htZW50OjZiYjQ5ZDVlPOST 200 in 1,400 ms(simulated)
Request
3 headers · bodyResponse
200 OKResponse: 200 OK after 1,400 ms.
What is the Resumable Upload API?
The Resumable Upload API is a Graph API mechanism for uploading a file in a session that can be paused and continued. It returns a file handle: an opaque string that other Graph API calls accept in place of the file itself. On the WhatsApp Business Platform, it is how you supply sample media when creating a message template with an image, video or document header (the header_handle in the template's example). It is also how you set a business profile photo (profile_picture_handle).
The flow has two calls. First, POST /{APP_ID}/uploads opens an upload session and returns an ID of the form upload:<SESSION_ID>. It takes file_name, file_length (in bytes) and file_type, which is one of application/pdf, image/jpeg, image/jpg, image/png or video/mp4. Second, POST /upload:<SESSION_ID> sends the binary file, with a file_offset header (0 for a fresh upload) and the token in Authorization: OAuth {ACCESS_TOKEN}. The response is {"h": "<FILE_HANDLE>"}. If the connection drops, GET /upload:<SESSION_ID> returns the file_offset reached so far, and you POST the remainder from there.
It is easy to confuse with media upload. Media upload (POST /{PHONE_NUMBER_ID}/media) stores a file for messaging and returns a media ID you put in a message, and those IDs expire after 30 days. The Resumable Upload API is addressed to your Meta App ID, not a phone number. It returns a handle for asset definitions such as template samples and profile photos, not for sending. A media ID will not work as a header_handle, and a handle cannot be sent as a message.
Key takeaways
- The Resumable Upload API turns a file into a handle that Graph API objects can reference, such as a template's header sample.
- It takes two steps:
POST /{APP_ID}/uploadsopens a session, thenPOST /upload:{SESSION_ID}sends the bytes and returnsh. - Interrupted uploads can resume: GET the session to read
file_offset, then POST the rest from that offset. - Supported types are PDF, JPEG, PNG and MP4, and the upload step authenticates with
Authorization: OAuth {token}. - A handle is not a media ID. Media IDs come from
/{PHONE_NUMBER_ID}/mediaand are used to send messages.
Operating sequence
How does the Resumable Upload API work?
Open a session on your app
POST /{APP_ID}/uploads?file_name=…&file_length=…&file_type=image/jpeg, sent with your access token, returns{"id": "upload:<SESSION_ID>"}.Send the bytes
Call
POST /upload:<SESSION_ID>with the headersAuthorization: OAuth {ACCESS_TOKEN}andfile_offset: 0, and the raw file as the body.Keep the handle
The response
{"h": "…"}is the file handle. Store it with the template draft that will use it.Resume if interrupted
GET /upload:<SESSION_ID>returnsfile_offset. POST the remaining bytes from that offset instead of starting over.Reference the handle
Put it in the template's header component as
example.header_handle, or inprofile_picture_handlewhen you update the business profile.
Implementation
The Resumable Upload API in code
# Step 1: open an upload session on your appcurl -X POST "https://graph.facebook.com/v26.0/{APP_ID}/uploads?file_name=diwali-header.jpg&file_length=482133&file_type=image/jpeg" \-H "Authorization: OAuth {ACCESS_TOKEN}"# → {"id": "upload:{SESSION_ID}"}# Step 2: send the bytes, starting at offset 0curl -X POST "https://graph.facebook.com/v26.0/upload:{SESSION_ID}" \-H "Authorization: OAuth {ACCESS_TOKEN}" \-H "file_offset: 0" \--data-binary @diwali-header.jpg# → {"h": "2:c2FtcGxl..."}
Note the `OAuth` scheme in the header, as Meta's docs show it. `file_length` is the exact size in bytes.
Specifications
The Resumable Upload API: key facts and limits
- Start
- POST /{APP_ID}/uploads (file_name, file_length, file_type)
- Upload
- POST /upload:{SESSION_ID} with a file_offset header
- Auth header
- Authorization: OAuth {ACCESS_TOKEN}
- Token type
- A user access token, per Meta's upload guide
- Returns
- {"h": "<FILE_HANDLE>"}
- Resume
- GET /upload:{SESSION_ID} → file_offset
- File types
- PDF, JPEG/JPG, PNG, MP4
- Used for
- Template header_handle, profile_picture_handle
Failure modes
Common mistakes with the Resumable Upload API
Using a media ID as a header sample
IDs from
/{PHONE_NUMBER_ID}/mediaare for sending messages. Template creation needs a handle from this flow inexample.header_handle.Sending the token with the Bearer scheme
Meta's docs specify
Authorization: OAuth {ACCESS_TOKEN}for the upload step, and say the call fails without the token in the header. Copy that format exactly.Declaring the wrong length or type
The session is opened for a declared size and MIME type. Take
file_lengthfrom the file's actual size in bytes and matchfile_typeto its real content, or the upload will not complete cleanly.Restarting instead of resuming
Re-uploading a large video from byte zero after a timeout wastes time and bandwidth. Read
file_offsetfrom the session and send only what is missing.
Design intent
Why does the Resumable Upload API matter?
Creating a template with a media header fails without a valid sample handle. Teams that automate template management at scale, with dozens of festive variants or regional language versions, go through this flow constantly. Understanding it avoids the classic dead ends: using a media ID where a handle is required, sending the token with the wrong scheme, or re-uploading a large video from scratch after a dropped connection instead of resuming.
Example: Forty festive headers in Ahmedabad
An Ahmedabad sweet-shop chain creates forty Diwali marketing templates, one per outlet, each with its own photo header. Its script opens an upload session on the integration's App ID for each image, uploads the JPEG, and writes the returned handle into each template's header_handle before submitting it for review. When a 14 MB shop-tour video drops halfway on a patchy office connection, the script reads file_offset from the session and sends only the remaining bytes. An earlier attempt that reused message media IDs had failed at template creation.
Recommended practice
Best practices for the Resumable Upload API
- Store each handle with the template draft it belongs to, so a rejected template can be fixed and resubmitted without hunting for the file.
- Check size and type locally against the header's media limits before opening a session.
- Log session IDs, because they are what you need to resume.
- Make sample media representative, since reviewers judge the template with it.