Messaging API
Message History API
Get a list of the messages in your workspace for a date range, with the status of each one. Use it to reconcile what was sent, build reports or find failed messages to retry.
https://wbiztool.com/api/v1/report/Body: JSON (needed for pages after the first) or form fields
The history covers every message in the workspace of your API key, whether it was sent through the API, the dashboard or a campaign. Results come 200 per page, oldest first. The same data is available on the Reports page.
Quick example#
curl -X POST https://wbiztool.com/api/v1/report/ \
-H "Content-Type: application/json" \
-d '{
"client_id": 12345,
"api_key": "YOUR_API_KEY",
"start_date": "01-09-2026",
"end_date": "08-09-2026",
"page": 1
}'import requests
page = 1
history = []
while True:
response = requests.post(
"https://wbiztool.com/api/v1/report/",
json={
"client_id": 12345,
"api_key": "YOUR_API_KEY",
"start_date": "01-09-2026",
"end_date": "08-09-2026",
"page": page, # must be a JSON number, not a string
},
timeout=60,
)
result = response.json() # read the body even when the HTTP code is 400
if result.get("message") != "Success" or "total" not in result:
print("Failed:", result.get("message", "no message in response"))
break
history.extend(result["history"])
if page * 200 >= result["total"]:
break
page += 1
failed = [m for m in history if m["message_status"] == "Failed"]
print(len(history), "messages,", len(failed), "failed")// Node.js 18+ (built-in fetch). Save as .mjs to use top-level await.
const history = [];
let page = 1;
while (true) {
const response = await fetch("https://wbiztool.com/api/v1/report/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: 12345,
api_key: "YOUR_API_KEY",
start_date: "01-09-2026",
end_date: "08-09-2026",
page, // must be a JSON number, not a string
}),
});
const result = await response.json(); // read the body even when the HTTP code is 400
if (result.message !== "Success" || !("total" in result)) {
console.error("Failed:", result.message ?? "no message in response");
break;
}
history.push(...result.history);
if (page * 200 >= result.total) break;
page += 1;
}
const failed = history.filter((m) => m.message_status === "Failed");
console.log(`${history.length} messages, ${failed.length} failed`);<?php
$history = [];
$page = 1;
do {
$ch = curl_init('https://wbiztool.com/api/v1/report/');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'client_id' => 12345,
'api_key' => 'YOUR_API_KEY',
'start_date' => '01-09-2026',
'end_date' => '08-09-2026',
'page' => $page, // an integer, so json_encode sends a number
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($result['message'] ?? '') !== 'Success' || !isset($result['total'])) {
echo 'Failed: ' . ($result['message'] ?? 'no message in response');
break;
}
$history = array_merge($history, $result['history']);
$page++;
} while (($page - 1) * 200 < $result['total']);
echo count($history) . ' messages';Replace 12345 and YOUR_API_KEY with your own values. See Authentication for where to find them.
Request parameters#
Authentication
client_idintegerrequiredYour API Client ID from Settings → API keys.
api_keystringrequiredYour API key from the same page.
Filters
start_datestringrequiredFirst day to include, in
DD-MM-YYYYformat, for example01-09-2026.end_datestringrequiredEnd of the range, in
DD-MM-YYYYformat. This day itself isn't included. See Date range.whatsapp_clientintegeroptionalOnly return messages sent from this WhatsApp number, using its ID from WhatsApp settings. Leave it out to get messages from all your numbers.
pageintegeroptionalPage number, starting at
1(the default). Each page holds up to 200 messages. Send it as a JSON number.0or a negative number returnstotalwith an emptyhistory.
Date range#
Dates are read as midnight at the start of that day in India Standard Time (IST, UTC+5:30), and messages are matched by when they were created (queued or scheduled), not when they were sent. The range runs from start_date 00:00 up to end_date 00:00, so:
"start_date": "01-09-2026", "end_date": "08-09-2026"returns 1 to 7 September. 8 September isn't included.- To get a single day, set
end_dateto the next day:"start_date": "15-09-2026", "end_date": "16-09-2026". - If both dates are the same, you get no messages.
Pagination#
Each response contains total, the number of messages in the whole range, and up to 200 of them in history. Request page 2, 3 and so on until page × 200 is at least total.
Response#
A successful request returns HTTP 200:
{
"message": "Success",
"status": 0,
"total": 3,
"history": [
{ "id": 9817263, "msg_type": "Text", "contact": "919876543210", "message_status": "Sent" },
{ "id": 9817264, "msg_type": "File", "contact": "919812345670", "message_status": "Failed" },
{ "id": 9817265, "msg_type": "Image", "contact": "Sales Team Mumbai", "message_status": "Pending" }
]
}
| Field | Type | Description |
|---|---|---|
message | string | Success when the request worked, otherwise the error. |
status | integer | Always 0. Don't use it to detect success. |
total | integer | Number of messages in the date range across all pages. Only present on success. |
history | array | Up to 200 messages on this page, oldest first. Empty when there's an error. |
history[].id | integer | Message ID, the same as the msg_id returned when it was sent. |
history[].msg_type | string | Text, Image or File. |
history[].contact | string | The recipient's phone number with country code, or the group name for group messages. |
history[].message_status | string | See the table below. |
Message status values#
message_status | Meaning |
|---|---|
Pending | Queued or scheduled, not sent yet (status 0). |
Sent | Sent from your WhatsApp number (status 1). |
Delivered | Reserved, not currently returned. |
Read | Reserved, not currently returned. |
Failed | Couldn't be sent, or sending was interrupted (status 2). Use Message status to see the error. |
Cancelled | Cancelled before it was sent (status 3). |
Expired | Not sent before its expire_after_seconds deadline (status 4). |
Delivery and read ticks aren't recorded at the moment, so sent messages always show as Sent. Delivered and Read are reserved values; if they ever appear, treat them as Sent.
Errors#
Errors return HTTP 200 with status set to 0, unless noted:
{ "message": "Error", "status": 0, "history": [] }
| Message | How to fix it |
|---|---|
Error | start_date or end_date is missing or not in DD-MM-YYYY format, the JSON body isn't valid (often a trailing comma), or the request wasn't a POST. |
Auth Error | Send both client_id and api_key. |
Invalid Client Id | Send client_id as a number. Returned with HTTP 403, without history. |
Auth Error: invalid api key | Check the key exists, hasn't been deleted and belongs to this client_id. Returned with HTTP 400, without history. |
Demo Account can not access apis | Use a regular account. |
Tips#
- Pull history in small ranges: a day or a week at a time keeps the number of pages low.
- Find failed messages: filter
historyforFailed, then call Message status with eachidto see why it failed. Before you retry, check theerror:Sending was interrupted and may have been delivered…means the recipient may already have the message. - Old messages are purged: messages that reached a final status and haven't changed for about 90 days may be purged and no longer appear here, as are messages still queued 90 days after they were created or scheduled, on a number that's disconnected or deleted.
- Real-time tracking: to react as messages are sent, pass a
webhookwhen you send the message instead of polling this endpoint.
