# Get when a watched brand sends

`GET /v1/email/competitive/watchlist/brands/{watchlist_brand_id}/send-time`

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.

## Code samples

### TypeScript

```ts
// 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);
```

### Python

```py
# 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())
```

### Go

```go
// 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))
```

### PHP

```php
// 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);
```

### CLI

```sh
bird email competitive watchlist brands send-time <watchlist-brand-id>
```

### cURL

```sh
curl -X GET "https://us1.platform.bird.com/v1/email/competitive/watchlist/brands/{watchlist_brand_id}/send-time" \
  -H "Authorization: Bearer $TOKEN"
```

## Example response `200`

```json
{
  "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
  }
}
```

## Path 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 body

- `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.
- `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.
- `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.
- `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

- [Should I use a Bird SDK or call the API directly?](/explained/platform/should-i-use-an-sdk-or-call-the-api-directly) (answer)
- [Build your first integration](/learn/paths/integration) (course)
- [Send your first email](/docs/get-started/send-your-first-email) (docs)

[Get an implementation brief](/learn/workspace?topic=api-basics)
