Upload template media
curl --request POST \
--url https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file' \
--form type=image/jpegimport requests
url = "https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = { "type": "image/jpeg" }
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
form.append('type', 'image/jpeg');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nimage/jpeg\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nimage/jpeg\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nimage/jpeg\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nimage/jpeg\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"h": "4::aW1hZ2UvanBlZw==:ARZ...:e:1770000000:..."
}{
"error": 123,
"message": "<string>"
}{
"error": 123,
"message": "<string>"
}{
"error": 123,
"message": "<string>"
}{
"error": 123,
"message": "<string>"
}{
"error": 123,
"message": "<string>"
}Templates (WhatsApp)
Upload media
Get the header_handle for templates with an image, video or document HEADER
POST
/
whatsapp
/
businesses
/
{wabaId}
/
templates
/
media
Upload template media
curl --request POST \
--url https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file' \
--form type=image/jpegimport requests
url = "https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = { "type": "image/jpeg" }
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
form.append('type', 'image/jpeg');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nimage/jpeg\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nimage/jpeg\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nimage/jpeg\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.omni.z-api.io/whatsapp/businesses/{wabaId}/templates/media")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nimage/jpeg\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"h": "4::aW1hZ2UvanBlZw==:ARZ...:e:1770000000:..."
}{
"error": 123,
"message": "<string>"
}{
"error": 123,
"message": "<string>"
}{
"error": 123,
"message": "<string>"
}{
"error": 123,
"message": "<string>"
}{
"error": 123,
"message": "<string>"
}Concepts
Meta does not accept the file directly when creating the template. It requires aheader_handle produced by the Resumable Upload API, which depends on the application token — that is why the upload goes through here and not through your frontend.
This endpoint takes the file and returns the handle, ready to use.
When you need this
Only when the template has aHEADER component of type IMAGE, VIDEO or DOCUMENT. Templates with a text header, or with no header, need no upload.
Flow
1
Upload the file
POST /whatsapp/businesses/{wabaId}/templates/media with multipart/form-data, fields file and type.2
Keep the handle
The response returns the handle in the
h field.3
Use it on the HEADER component
Pass the handle as the HEADER
example in Create template:{
"type": "HEADER",
"format": "IMAGE",
"example": { "header_handle": ["4::aW1hZ2UvanBlZw==:ARZ..."] }
}
The handle expires. Upload the file and create the template in the same session — do not store the handle to use days later.
Besides
h, the API accepts the handle under the keys handle, header_handle, headerHandle and mediaHandle. Use h, which is the canonical form.Configuration prerequisite
The upload depends on the Meta App ID and App Secret registered on the account. Without them the call returns422 with MISSING_APP_CREDENTIALS.
This is configured in the dashboard, under Security — there is no public endpoint to register the app credentials. See also SDK origins, which covers the other prerequisite of the SDK flow.
Limits
| Item | Value |
|---|---|
| Maximum size | 100 MB |
| Required fields | file, type |
Authorizations
Secret Key generated in the Omni Z-API Security panel
Path Parameters
WABA ID (obtained via List WABAs)
Example:
"428083093730937"
Body
multipart/form-data
Response
Upload completed
Handle to use as the HEADER component example when creating the template