Skip to content

Glossary drawing · Graph API & SDKs

Resumable Upload API

Also called: Upload session · File handle upload · header_handle upload

Note 1 —A Graph API flow that uploads a file through a session on your Meta app and returns a file handle, used for template header samples and profile photos.

Term
Resumable Upload API
Abbreviation
—
Category
Graph API & SDKs
Level
Expert
Reviewed
Sheet no.
GLO-API-335
Drawn by
InfiQ Glossary

Drawing 1 · Interactive

Upload the bytes, get a file handle back

2 / 2
POSThttps://graph.facebook.com/v26.0/upload:MTphdHRhY2htZW50OjZiYjQ5ZDVl

POST 200 in 1,400 ms

Request

3 headers · body
POST /v26.0/upload:MTphdHRhY2htZW50OjZiYjQ5ZDVl HTTP/1.1
Host: graph.facebook.com
Authorization: OAuth {ACCESS_TOKEN}
file_offset: 0
<482,133 bytes of image/jpeg: diwali-header.jpg>

Response

200 OK
HTTP/1.1 200 OK
Content-Type: application/json
{
"h": "2:c2FtcGxl..."
}
Try another response
Step 2 of a resumable upload for a template header image. Send it to see the h handle come back, then toggle the resume check to see the file_offset a GET on the same session reports after a dropped connection.

Response: 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}/uploads opens a session, then POST /upload:{SESSION_ID} sends the bytes and returns h.
  • 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}/media and are used to send messages.

Operating sequence

How does the Resumable Upload API work?

  1. 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>"}.

  2. Send the bytes

    Call POST /upload:<SESSION_ID> with the headers Authorization: OAuth {ACCESS_TOKEN} and file_offset: 0, and the raw file as the body.

  3. Keep the handle

    The response {"h": "…"} is the file handle. Store it with the template draft that will use it.

  4. Resume if interrupted

    GET /upload:<SESSION_ID> returns file_offset. POST the remaining bytes from that offset instead of starting over.

  5. Reference the handle

    Put it in the template's header component as example.header_handle, or in profile_picture_handle when you update the business profile.

Implementation

The Resumable Upload API in code

# Step 1: open an upload session on your app
curl -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 0
curl -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}/media are for sending messages. Template creation needs a handle from this flow in example.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_length from the file's actual size in bytes and match file_type to 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_offset from 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.

Resumable Upload API FAQs

What is header_handle in a WhatsApp template?+
It is the file handle of the sample media for a template's image, video or document header. You get it from the Resumable Upload API and pass it in the header component's `example`.
Can I use a media ID instead of a file handle for template samples?+
No. Media IDs from the media endpoint are for sending messages. Template samples need a handle from the Resumable Upload API, which is a different flow with a different endpoint.
Which file types does the Resumable Upload API accept?+
PDF, JPEG (declared as jpeg or jpg), PNG and MP4, set in `file_type` when you open the session. Check the template header's own media limits as well before uploading.
Why is the upload addressed to my app ID and not my phone number ID?+
Because the handle defines assets such as templates and profiles, which are not tied to sending from one number. Media for messages goes to `/{PHONE_NUMBER_ID}/media` instead.

See InfiQ pricing

Put Resumable Upload API to work

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.