Media API
Upload media API
Upload an image, document, audio or video file to your workspace's Media directory and get back a download URL. Pass that URL to Send message, or to Create reminder as img_url, when you don't have your own file hosting.
https://wbiztool.com/api/v1/media/upload/Body: multipart/form-data
The file is stored straight away and the response includes its file_url. Uploaded files also appear on the Media directory page in your dashboard.
Quick example#
curl -X POST https://wbiztool.com/api/v1/media/upload/ \
-F client_id=12345 \
-F api_key=YOUR_API_KEY \
-F media_file=@./invoice-4821.pdfimport requests
with open("invoice-4821.pdf", "rb") as f:
response = requests.post(
"https://wbiztool.com/api/v1/media/upload/",
data={"client_id": 12345, "api_key": "YOUR_API_KEY"},
files={"media_file": f},
timeout=120,
)
try:
result = response.json()
except ValueError:
# A proxy can reject a very large upload with an HTML page before it reaches Wbiztool.
raise SystemExit(f"HTTP {response.status_code}: not JSON. Check the file size.")
if result["status"] == 1:
print("Uploaded:", result["data"]["file_url"])
else:
print("Failed:", result["message"])// Node.js 20+ (built-in fetch, FormData and fs.openAsBlob). Save as .mjs to use top-level await.
import { openAsBlob } from "node:fs";
const form = new FormData();
form.append("client_id", "12345");
form.append("api_key", "YOUR_API_KEY");
form.append("media_file", await openAsBlob("./invoice-4821.pdf"), "invoice-4821.pdf");
// Don't set Content-Type yourself; fetch adds the multipart boundary.
const response = await fetch("https://wbiztool.com/api/v1/media/upload/", { method: "POST", body: form });
const result = await response.json();
if (result.status === 1) {
console.log("Uploaded:", result.data.file_url);
} else {
console.error("Failed:", result.message);
}<?php
$ch = curl_init('https://wbiztool.com/api/v1/media/upload/');
curl_setopt_array($ch, [
CURLOPT_POST => true,
// An array body (not json_encode) makes cURL send multipart/form-data.
CURLOPT_POSTFIELDS => [
'client_id' => 12345,
'api_key' => 'YOUR_API_KEY',
'media_file' => new CURLFile('/path/to/invoice-4821.pdf', 'application/pdf', 'invoice-4821.pdf'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($result['status'] ?? 0) === 1) {
echo 'Uploaded: ' . $result['data']['file_url'];
} else {
echo 'Failed: ' . ($result['message'] ?? 'no response');
}Replace 12345 and YOUR_API_KEY with your own values. See Authentication for where to find them.
Request parameters#
Send the request as multipart/form-data. JSON bodies and query-string parameters aren't read by this endpoint.
client_idintegerrequiredYour API Client ID from Settings → API keys.
api_keystringrequiredYour API key from the same page.
media_filefilerequiredThe file to upload, up to 64 MB, and it must fit in the workspace's media storage. Its type is decided by the file name's extension, so the name must end in one of the supported extensions.
Supported file types#
| Category | Extensions |
|---|---|
| Images | .jpg, .jpeg, .png, .gif, .webp, .bmp |
| Documents | .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt, .csv |
| Audio | .mp3, .m4a, .wav, .ogg, .aac |
| Video | .mp4, .mov, .avi |
| Archives | .zip, .rar |
Images are stored with file_type set to image; everything else is file.
Storage per workspace#
All files in a workspace's Media directory share one storage limit, set by the workspace's plan. Files uploaded with this API and on the Media directory page both count, and deleting a file frees its space.
| Plan | Media storage |
|---|---|
| Free trial | 250 MB |
| Lite | 1 GB |
| Standard | 2 GB |
| Advanced | 5 GB |
| Enterprise | 10 GB |
| AppSumo Plan 1 and Plan 2 | 1 GB |
| AppSumo Plan 3 | 2 GB |
| AppSumo Plan 4 | 3 GB |
| AppSumo Plan 5 | 5 GB |
Older plans, and a workspace without an active plan, have 1 GB. An upload that doesn't fit is refused with Storage full: … (see Errors). A workspace that's already over its limit keeps its files, but can't add new ones until it has room for them.
Response#
A successful request returns HTTP 200:
{
"status": 1,
"message": "File uploaded successfully",
"data": {
"id": 5122,
"file_name": "media_12345_17895804001234_3f9a1c7e.pdf",
"original_file_name": "invoice-4821.pdf",
"file_url": "https://wbiztool-static.s3.ap-southeast-1.amazonaws.com/media/org_12345/media_12345_17895804001234_3f9a1c7e.pdf",
"file_type": "file",
"file_size": 248312,
"file_size_display": "242.5 KB",
"mime_type": "application/pdf",
"is_image": false,
"file_extension": "pdf",
"created_at": "2026-09-16T10:20:31.512934+00:00"
}
}
| Field | Type | Description |
|---|---|---|
status | integer | 1 if the file was uploaded, 0 if the request failed. |
message | string | File uploaded successfully, otherwise the error. |
data.id | integer | ID of the media file. Use it with Get media file. |
data.file_name | string | Name the file is stored under, generated by Wbiztool: media_, your workspace ID, the upload time and a random part, with the file's extension. |
data.original_file_name | string | The name of the file you uploaded. |
data.file_url | string | Direct download URL. Use it as img_url or file_url when sending. It's public and doesn't expire, but stops working when the file is deleted. It may be on media.wbiztool.com (our CDN) or wbiztool-static.s3.ap-southeast-1.amazonaws.com; both serve the same file, so store it as it is and don't build these URLs yourself. |
data.file_type | string | image or file. |
data.file_size | integer | Size in bytes. |
data.file_size_display | string | Readable size with one decimal, for example 242.5 KB. |
data.mime_type | string | MIME type worked out from the extension, for example application/pdf. |
data.is_image | boolean | true when file_type is image. |
data.file_extension | string | Lower-case extension of original_file_name, without the dot. |
data.created_at | string | Upload time, ISO 8601 in UTC with a +00:00 offset. |
Errors#
Errors also return HTTP 200, with status set to 0:
{ "status": 0, "message": "File is too large. Maximum size is 64MB. Your file: 71.3MB" }
| Message | How to fix it |
|---|---|
Only POST method is supported | Send a POST request. |
client_id is required | Add client_id as a form field. Check the body is multipart/form-data, not JSON. |
api_key is required | Add api_key as a form field. |
media_file is required | Send the file in a field named media_file. |
client_id must be a valid integer | Send client_id as a number. |
Invalid API key | Check the key exists and hasn't been deleted or disabled. |
Invalid client_id for this API key | The key belongs to a different client_id. |
File is too large. Maximum size is 64MB. Your file: …MB | Upload a file of 64 MB or less. |
File type not supported. Type detected: … | Use one of the supported extensions. |
Storage full: this workspace uses … of its … of media storage, and this file is …. Delete files you no longer need in the Media directory, or contact us if you need more space. | The file doesn't fit in the workspace's media storage. Delete files you no longer need on the Media directory page, or contact us for more space. |
Error uploading file: … | The upload couldn't be completed. Try again. You also get this if the API key isn't linked to a workspace; create a new key in the workspace you want to use. |
Upload and send#
Upload the file, then pass file_url to Send message: as img_url with msg_type 1 for images, or as file_url with msg_type 2 for other files.
import requests
BASE = "https://wbiztool.com/api/v1"
AUTH = {"client_id": 12345, "api_key": "YOUR_API_KEY"}
with open("invoice-4821.pdf", "rb") as f:
upload = requests.post(f"{BASE}/media/upload/", data=AUTH, files={"media_file": f}, timeout=120).json()
if upload["status"] != 1:
raise SystemExit("Upload failed: " + upload["message"])
media = upload["data"]
message = {
**AUTH,
"whatsapp_client": 678,
"country_code": "91",
"phone": "9876543210",
"msg": "Your invoice for order #4821 is attached.",
}
if media["is_image"]:
message.update(msg_type=1, img_url=media["file_url"])
else:
message.update(msg_type=2, file_url=media["file_url"], file_name=media["original_file_name"])
sent = requests.post(f"{BASE}/send_msg/", json=message, timeout=60).json()
print(sent)// Node.js 20+ (built-in fetch, FormData and fs.openAsBlob). Save as .mjs to use top-level await.
import { openAsBlob } from "node:fs";
const BASE = "https://wbiztool.com/api/v1";
const form = new FormData();
form.append("client_id", "12345");
form.append("api_key", "YOUR_API_KEY");
form.append("media_file", await openAsBlob("./invoice-4821.pdf"), "invoice-4821.pdf");
const upload = await (await fetch(`${BASE}/media/upload/`, { method: "POST", body: form })).json();
if (upload.status !== 1) throw new Error(`Upload failed: ${upload.message}`);
const media = upload.data;
const message = {
client_id: 12345,
api_key: "YOUR_API_KEY",
whatsapp_client: 678,
country_code: "91",
phone: "9876543210",
msg: "Your invoice for order #4821 is attached.",
...(media.is_image
? { msg_type: 1, img_url: media.file_url }
: { msg_type: 2, file_url: media.file_url, file_name: media.original_file_name }),
};
const sent = await fetch(`${BASE}/send_msg/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(message),
});
console.log(await sent.json());Every file type this API accepts keeps its extension when it's sent with msg_type 2, for example brochure.doc or catalogue.zip. WAV and OGG audio arrive as documents rather than playable audio, and fail with File type not supported when sent as an image (msg_type 1). MP3, M4A and AAC arrive as audio that plays in the chat, with the text as a separate message. See Sending images and files. The name is sent in lower case (Invoice-4821.PDF arrives as invoice-4821.pdf).
Images sent with msg_type 1 must be 16 MB or smaller. Larger images upload fine here but fail when sent, with File exceeds WhatsApp size limit (16MB max). Compress them first, or send them as a file with msg_type 2.
Tips#
- Reuse uploads. Store the
idorfile_urland send the same file many times instead of uploading it again. - Keep the original name meaningful.
original_file_nameis what you see in the dashboard and a good value forfile_namewhen sending. - Find earlier uploads with List media.
