Get when a watched brand sends
GET
/v1/email/competitive/watchlist/brands/{watchlist_brand_id}/send-time
// Requires Insights preview access for the organization.
const watchlist = await bird.email.competitive.watchlist.get({ range: 30 });
const entry = watchlist.data.find((row) => row.name === "Everlane" && row.watchlist_brand_id);
if (!entry?.watchlist_brand_id) throw new Error("Add Everlane to the watchlist first");
const watchlistBrandId = entry.watchlist_brand_id;
const report = await bird.email.competitive.watchlist.brands.sendTime(watchlistBrandId, { timezone: "UTC" });
console.log(report);# Requires Insights preview access for the organization.
watchlist = client.email.competitive.watchlist.get(range=30)
entry = next((row for row in watchlist.data if row.name == "Everlane" and row.watchlist_brand_id), None)
if entry is None or entry.watchlist_brand_id is None:
raise ValueError("Add Everlane to the watchlist first")
watchlist_brand_id = entry.watchlist_brand_id
report = client.email.competitive.watchlist.brands.send_time(watchlist_brand_id, timezone="UTC")
print(report.model_dump_json())// Requires Insights preview access for the organization.
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
watchlist, err := client.Email.Competitive.Watchlist.Get(ctx, bird.EmailCompetitiveWatchlistGetParams{Range: 30})
if err != nil {
log.Fatal(err)
}
watchlistBrandID := ""
if watchlist.Data != nil {
for _, row := range *watchlist.Data {
if row.Name != nil && *row.Name == "Everlane" && row.WatchlistBrandId != nil {
watchlistBrandID = string(*row.WatchlistBrandId)
break
}
}
}
if watchlistBrandID == "" {
log.Fatal("Add Everlane to the watchlist first")
}
report, err := client.Email.Competitive.Watchlist.Brands.SendTime(ctx, watchlistBrandID, bird.EmailCompetitiveWatchlistBrandsSendTimeParams{Timezone: "UTC"})
if err != nil {
log.Fatal(err)
}
encoded, err := json.MarshalIndent(report, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(encoded))// Requires Insights preview access for the organization.
$watchlist = $bird->email->competitive->watchlist->get(['range' => 30]);
$watchlistBrandId = null;
foreach ($watchlist->getData() ?? [] as $row) {
if ($row->getName() === 'Everlane' && $row->getWatchlistBrandId() !== null) {
$watchlistBrandId = $row->getWatchlistBrandId();
break;
}
}
if ($watchlistBrandId === null) {
throw new \RuntimeException('Add Everlane to the watchlist first');
}
$report = $bird->email->competitive->watchlist->brands->sendTime($watchlistBrandId, ['timezone' => 'UTC']);
var_dump($report);bird email competitive watchlist brands send-time <watchlist-brand-id>curl -X GET "https://us1.platform.bird.com/v1/email/competitive/watchlist/brands/{watchlist_brand_id}/send-time" \
-H "Authorization: Bearer $TOKEN"Response200
{
"period": {
"days": 30,
"from": "2026-07-13T09:00:00Z",
"to": "2026-08-12T09:00:00Z"
},
"timezone": "America/New_York",
"panel_status": "ok",
"cells": [
{
"weekday": "tuesday",
"hour": 13,
"share_percent": 3.4,
"intensity": 0.55,
"sample_days": 13
}
],
"peak_send_window": {
"start_hour": 13,
"end_hour": 14,
"share_percent": 13.1
}
}
Returns how a watched brand's sending is spread across the week: one figure per
weekday and hour of the day, over the last 90 days, with the hour of the day it
sends most of its mail in.
Each hour counts when the brand sent, not when its subscribers opened or
received the mail. It answers "when does this brand mail its list", which is what
a competing send has to be timed against. It says nothing about how busy a
subscriber's inbox was at that hour.
Hours are reported in the timezone you ask for, echoed back in timezone, and the
week is folded into that zone before it is totalled, so a send at 02:00 UTC on
Monday counts as Sunday evening for a reader in New York, which is when it arrived
for them. Label an axis from timezone rather than from what you asked for: a
response the panel could not answer reports UTC regardless.
Expect the weekday axis to look flat. For most brands the hour of the day is where
the pattern is, and which day of the week it is barely moves the figure; a grid
with little variation down its rows is a real finding about how the brand mails
rather than a gap in the data.
API-key calls require Insights preview access for your organization.
Parameters
watchlist_brand_id
string
The watchlist entry whose sending pattern to return.
Query Parameters
timezone
string
IANA timezone identifier to report send times in; defaults to UTC. The grid is folded into this zone before it is summed, so a send lands on the weekday and hour it happened at locally rather than the one it happened at in UTC. A zone this API does not know returns 422 rather than falling back to UTC, so an axis is never labelled with a zone the figures were not folded into.
Response Payload
period
object
required
The period the grid covers. It is always the last 90 days, whatever range the
rest of the brand's figures are shown over: an hour of the week comes round
about thirteen times in 90 days and once in a week, and a pattern drawn from
one observation per cell is noise.
Two things differ from the other competitive reads. It ends at the start of a
day rather than at the moment of the request, and the panel answers repeat
requests from a cache it holds for a day, so two requests a minute apart return
identical figures and this grid can be up to a day behind the figures shown
beside it.
Show child attributes
period.days
integer
required
Length of the period in days.
period.from
string
required
Start of the period, inclusive.
period.to
string
required
End of the period, exclusive. Daily figures therefore run through the previous whole UTC day and never include the one in progress.
timezone
string
required
The timezone the hours are reported in. Label the grid from this rather than from what was requested: a response the panel could not answer reports UTC whatever was asked for.
panel_status
string
required
Why the grid is empty, when it is.
Possible values: ok, not_in_panel, no_data, unavailable
cells
array of object
required
Every weekday and hour of the week, Monday first and hour ascending: 168 in all, whether or not the brand sent in them, so the grid needs no filling in. Empty when there was nothing to read, which panel_status explains.
Show child attributes
cells.weekday
string
required
The day of the week this hour falls on.
Possible values: monday, tuesday, wednesday, thursday, friday, saturday, sunday
cells.hour
integer
required
The hour this cell covers, in the timezone the response reports. 13 covers 13:00 to 14:00.
cells.share_percent
number
required
Share of everything the brand sent over the period that fell in this hour. It is 0 for an hour the brand demonstrably did not send in, which on a disciplined sender is the most useful thing this grid says.
cells.intensity
number
required
How strongly the brand sends in this hour, against its own busiest hour at 1.
It is this cell's sending per sample_days divided by the busiest cell's, so it
is derivable from the two numbers beside it and reconciles with them rather than
competing: it is published because that correction is easy to get wrong, not
because it knows anything they do not.
Shade a cell by this rather than by share_percent: the period holds one more
of some weekdays than others, so a share compares an hour that came round
thirteen times against one that came round twelve.
cells.sample_days
integer
required
How many days of the period fell on this weekday, whether or not the brand sent on them. It is what separates an hour the brand is quiet in from one there was little chance to observe.
peak_send_window
nullable object
required
The hour of the day the brand sends most of its mail in, totalled across the whole week, or null when nothing was observed. It carries no weekday: for most brands the hour of the day is where the pattern is and the day of the week barely moves, so naming a busiest weekday would give a figure more meaning than it has. It is also not always the darkest cell, on the same reasoning: one busy Wednesday can outweigh the hour the brand mails in every single day.
Show child attributes
peak_send_window.start_hour
integer
required
The first hour of the window, in the timezone the response reports.
peak_send_window.end_hour
integer
required
The hour the window ends at, exclusive: a window of 13 to 14 covers 13:00 to
14:00. The window is always one hour wide on this endpoint, so this is always the
hour after start_hour. The pair is kept rather than collapsed because the panel
computes the window at whatever width it was asked for, and only this endpoint
pins that to an hour.
It can therefore be lower than start_hour in exactly one case: a peak at 23:00,
whose window runs past midnight and ends at 0.
peak_send_window.share_percent
number
required
Share of everything the brand sent over the period that fell in this window.
This is the panel's own figure, while a cell's share_percent is recomputed from
the cells in the response. Adding up this hour's seven cells should therefore land
on this number but is not guaranteed to; where they disagree, this one is the
panel's answer about its own peak and the cells are the arithmetic behind the grid.
Related resources
Continue with the documentation, guides and examples for this topic. Resources are in English.
Understand the conceptShould I use a Bird SDK or call the API directly?Follow the learning pathBuild your first integrationImplementation guideSend your first email
Get an implementation brief