Sign inGet Started

Get the campaigns a watched brand sent

GET
/v1/email/competitive/watchlist/brands/{watchlist_brand_id}/campaigns
// 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;
for await (const campaign of bird.email.competitive.watchlist.brands.campaigns.list(watchlistBrandId, { range: 30, limit: 25 })) {
  console.log(campaign.id);
}
Response200
{
  "period": {
    "days": 30,
    "from": "2026-07-13T09:00:00Z",
    "to": "2026-08-12T09:00:00Z"
  },
  "panel_status": "ok",
  "captured": 38,
  "promo_rate": 0.64,
  "truncated": false,
  "data": [
    {
      "id": "3914827265",
      "subject": "The Summer Sale: 40% off everything",
      "sent_at": "2026-08-09T14:02:00Z",
      "image_url": "https://images.example.com/creatives/c154c8c4-6356-40e6-92d2-7c6727ec36ca.jpg",
      "reach": 410000,
      "read_rate": 0.228,
      "has_creative": true,
      "discount_percent": 40,
      "inbox_rate": 0.879,
      "spam_rate": 0.121
    }
  ],
  "next_cursor": "eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9",
  "prev_cursor": null,
  "refresh_cursor": "eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9"
}

Returns a page of campaigns an email panel observed a watched brand sending over the period, in the requested order. Sampled statistics describe eligible campaigns in the first 100 newest panel rows for each tracked domain, independently of the page.

Each campaign is one send the panel saw reach real inboxes, so the subject and timing are what the brand's subscribers received rather than anything the brand published. Volume and read rate are panel estimates, fetched while the request runs.

The panel folds a day's low-volume sending into a single synthetic entry with no creative and no volume. Those are left out, so the count here is lower than the number of rows the panel holds and describes campaigns a person would recognise as campaigns.

API-key calls require Insights preview access for your organization.

Parameters

watchlist_brand_idstring

The watchlist entry whose campaigns to return.

Query Parameters

rangeinteger

How many days back the response covers, counting from now. One of three fixed trend windows rather than an open date range, matching how a competitive-intelligence chart is read. Defaults to 30.

Possible values: 7, 30, 90

sortstring

Field to sort campaigns by.

Possible values: sent_at

orderstring

Sort direction. Defaults to desc, which sorts from newest to oldest or largest to smallest, depending on the selected sort field.

Possible values: asc, desc

limitinteger

Maximum number of items to return per page.

starting_afterstring

Cursor from the next_cursor field of a previous list response. Returns items immediately after the cursor position in the current sort order.

ending_beforestring

Cursor from the prev_cursor or refresh_cursor field of a previous list response. Returns items immediately before the cursor position in the current sort order. prev_cursor returns the preceding page. refresh_cursor anchors at the first row of that response, which on a newest-first sort is how to fetch the items that have appeared since.

Response Payload

period
object
required

The rolling period used for this request.

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.

panel_status
string
required

For this campaign feed, no_data means the requested page is empty; it does not mean the whole period has no campaigns.

Possible values: ok, not_in_panel, no_data, unavailable

captured
integer
required

Number of eligible campaigns in the first 100 newest panel rows for each tracked domain. This sampled value is independent of the returned page.

promo_rate
nullable number
required

Fraction of captured campaigns whose subject contains a recognized percentage-discount offer. Dollar discounts and free-shipping offers do not count. Null when captured is zero. This sampled value is independent of the returned page.

truncated
boolean
required

Whether the sampled statistics or returned page omit part of the requested collection. Use next_cursor to determine whether another page is available.

data
array of object
required

Campaigns in this page, in the requested order.

Show child attributes
next_cursor
nullable string
required

Cursor for the next page. Pass back as starting_after to advance forward. null when no next page exists.

prev_cursor
nullable string
required

Cursor for the previous page. Pass back as ending_before to step backward. null when no previous page exists.

refresh_cursor
nullable string
required

Refresh anchor, the first row of this response. Pass back as ending_before to fetch what precedes it in the current sort order. On a newest-first sort those are the items that have appeared since; on any other sort they are the items that sort earlier, so refreshing such a list means re-fetching it instead. Non-null whenever data is non-empty; null only on an empty page. Distinct from prev_cursor.

Continue with the documentation, guides and examples for this topic.