Fetch broadcast logs
Get
https://api.bunce.so/v1/broadcast/logsReturns a company's delivery and engagement logs — one row per event that happened to a message on its way to a recipient (sent, delivered, opened, clicked, bounced, failed, complained, unsubscribed) — with the originating broadcast and the recipient inlined on each log. Events are available for the last 3 months only.
Requests are automatically scoped to your own company, so you only ever see your own logs.
Request
Query Parameters
All parameters are optional and combine with AND.
statuses array<enum> | Filter by event type. Repeatable: statuses[]=sent&statuses[]=opened. |
channel enum | Filter by message channel. |
broadcast_id string<uuid> | Return only logs for one specific broadcast. |
customer_ids array<string> | Return only logs for the given recipients. Repeatable: customer_ids[]=...&customer_ids[]=.... |
from date | Inclusive lower bound on the event date. Must fall within the last 3 months. |
to date | Inclusive upper bound on the event date. Must be on or after from. |
per_page int | Page size, 1..100. If not specified we use a default value of 20. |
cursor string | Specify exactly what cursor you want to retrieve. |
Status values
sent, delivered, opened, clicked, bounced, failed, complained, unsubscribed
Channel values
email, whatsapp, sms, push_notification
Date range
Logs are available for the last 3 months only — events older than that are not returned.
- If you omit both
fromandto, you get the last 3 months of activity. Supplying either bound narrows the range to your own dates. fromandtomust fall within the last 3 months; dates outside it are rejected.- If you supply
toearlier thanfrom, the request fails validation (422).
curl --request GET \
--url 'https://api.bunce.so/v1/broadcast/logs' \
--header 'Accept: application/json' \
--header 'X-Authorization: sk_live_************************'import from 'node-fetch'
let = 'https://api.bunce.so/v1/broadcast/logs'
let = {
: 'GET',
: {
: 'application/json',
'X-Authorization': 'sk_live_************************',
},
}
(, )
.then(() => .json())
.then(() => .())
.catch(() => .('error:' + ))import (
"fmt"
"net/http"
"io/ioutil"
)
url := "https://api.bunce.so/v1/broadcast/logs"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Accept", "application/json")
req.Header.Add("X-Authorization", "sk_live_************************")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))Response
Logs are returned newest-first (action_at descending).
id string | Unique id of the log entry. |
status enum | Event type — one of the Status values. |
action_at datetime | When the event occurred (ISO-8601). |
response string|null | Extra detail attached to the event, when available. For clicked logs this is the URL the recipient clicked. |
broadcast object|null | The broadcast that produced this event. null if it can no longer be resolved. |
customer object|null | The recipient of the message. null if it can no longer be resolved. |
broadcast
id string | Broadcast id. |
channel enum | Channel the broadcast was sent on. |
subject string|null | Subject line (email). |
message_type enum | One of broadcast or transactional. |
created_at datetime | When the broadcast was created. |
customer
customer_id string | The recipient's external Bunce customer identifier. |
email string|null | Recipient email. |
phone_no string|null | Recipient phone number. |
first_name string|null | Recipient first name. |
last_name string|null | Recipient last name. |
meta
has_next_page boolean | Whether more results exist after this page. |
has_prev_page boolean | Whether results exist before this page. |
next_page_cursor string|null | Pass as cursor to fetch the next page. |
prev_page_cursor string|null | Pass as cursor to fetch the previous page. |
per_page int | Page size actually used. |
{
"success": true,
"data": [
{
"id": "01J2XK9F3T8QW2E6R1Y5M7C4P0",
"status": "sent",
"action_at": "2026-10-05T09:31:05+00:00",
"response": "Message queued",
"broadcast": {
"id": "3f1b2a7e-8c9d-4e2a-9b1f-0a7c5d6e8f90",
"channel": "email",
"subject": "Your October newsletter",
"message_type": "broadcast",
"created_at": "2026-10-05T09:30:00+00:00"
},
"customer": {
"customer_id": "CUS-EXTERNAL-ID",
"email": "ada@example.com",
"phone_no": "+2348012345678",
"first_name": "Ada",
"last_name": "Lovelace"
}
}
],
"meta": {
"has_next_page": false,
"has_prev_page": false,
"next_page_cursor": null,
"prev_page_cursor": null,
"per_page": 20
},
"message": "Timeline logs fetched successfully"
}{
"success": false,
"message": "X-Authorization is required in headers"
}{
"success": false,
"message": "The selected statuses.0 is invalid.",
"errors": {
"statuses.0": ["The selected statuses.0 is invalid."]
}
}WARNING
broadcast and customer are returned as null — never omitted — when the underlying record can no longer be resolved, so always null-check before reading nested fields.
Examples
Messages SENT to a customer within a timeframe
Returns the sent events for a given recipient between from and to.
curl --request GET \
--url 'https://api.bunce.so/v1/broadcast/logs?statuses[]=sent&customer_ids[]=CUS-EXTERNAL-ID&from=2026-10-01&to=2026-10-10' \
--header 'Accept: application/json' \
--header 'X-Authorization: sk_live_************************'Messages OPENED by a customer
Each item represents one open event, so a recipient who opened the same message several times appears multiple times (newest first).
curl --request GET \
--url 'https://api.bunce.so/v1/broadcast/logs?statuses[]=opened&customer_ids[]=CUS-EXTERNAL-ID&from=2026-10-01&to=2026-10-10' \
--header 'Accept: application/json' \
--header 'X-Authorization: sk_live_************************'Messages CLICKED by a customer
For clicked logs, response holds the link the recipient clicked.
curl --request GET \
--url 'https://api.bunce.so/v1/broadcast/logs?statuses[]=clicked&customer_ids[]=CUS-EXTERNAL-ID' \
--header 'Accept: application/json' \
--header 'X-Authorization: sk_live_************************'{
"success": true,
"data": [
{
"id": "01J2XQ7D2N5V8B1M3Z6T9K4W7H",
"status": "clicked",
"action_at": "2026-10-05T14:05:12+00:00",
"response": "https://example.com/offer",
"broadcast": {
"id": "3f1b2a7e-8c9d-4e2a-9b1f-0a7c5d6e8f90",
"channel": "email",
"subject": "Your October newsletter",
"message_type": "broadcast",
"created_at": "2026-10-05T09:30:00+00:00"
},
"customer": {
"customer_id": "CUS-EXTERNAL-ID",
"email": "ada@example.com",
"phone_no": "+2348012345678",
"first_name": "Ada",
"last_name": "Lovelace"
}
}
],
"meta": {
"has_next_page": false,
"has_prev_page": false,
"next_page_cursor": null,
"prev_page_cursor": null,
"per_page": 20
},
"message": "Timeline logs fetched successfully"
}Delivery status across a single broadcast
curl --request GET \
--url 'https://api.bunce.so/v1/broadcast/logs?broadcast_id=3f1b2a7e-8c9d-4e2a-9b1f-0a7c5d6e8f90&channel=email' \
--header 'Accept: application/json' \
--header 'X-Authorization: sk_live_************************'Paging with the cursor
curl --request GET \
--url 'https://api.bunce.so/v1/broadcast/logs?statuses[]=delivered&per_page=2' \
--header 'Accept: application/json' \
--header 'X-Authorization: sk_live_************************'Then follow up with the returned meta.next_page_cursor, keeping your filters:
curl --request GET \
--url 'https://api.bunce.so/v1/broadcast/logs?statuses[]=delivered&per_page=2&cursor=eyJhY3Rpb25fYXQi...' \
--header 'Accept: application/json' \
--header 'X-Authorization: sk_live_************************'When no logs match the filters, data is an empty array and the request is still 200 OK.