Get the campaigns a watched brand sent
/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);
}# 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
for campaign in client.email.competitive.watchlist.brands.campaigns.list(watchlist_brand_id, range=30, limit=25):
print(campaign.id)// 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")
}
for campaign, err := range client.Email.Competitive.Watchlist.Brands.Campaigns.List(ctx, watchlistBrandID, bird.EmailCompetitiveWatchlistBrandsCampaignsListParams{Range: 30, Limit: 25}) {
if err != nil {
log.Fatal(err)
}
if campaign.Id != nil {
fmt.Println(*campaign.Id)
}
}// 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');
}
foreach ($bird->email->competitive->watchlist->brands->campaigns->list($watchlistBrandId, ['range' => 30, 'limit' => 25]) as $campaign) {
echo $campaign->getId(), "\n";
}bird email competitive watchlist brands campaigns list <watchlist-brand-id>curl -X GET "https://us1.platform.bird.com/v1/email/competitive/watchlist/brands/{watchlist_brand_id}/campaigns" \
-H "Authorization: Bearer $TOKEN" \
--url-query "range=30" \
--url-query "sort=sent_at" \
--url-query "order=desc" \
--url-query "limit=25"{
"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.
Parámetros
watchlist_brand_idstringThe watchlist entry whose campaigns to return.
Parámetros de consulta
rangeintegerHow 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
sortstringField to sort campaigns by.
Possible values: sent_at
orderstringSort direction. Defaults to desc, which sorts from newest to oldest or largest to smallest, depending on the selected sort field.
Possible values: asc, desc
limitintegerMaximum number of items to return per page.
starting_afterstringCursor from the next_cursor field of a previous list response. Returns items immediately after the cursor position in the current sort order.
ending_beforestringCursor 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.
Carga de respuesta
periodThe rolling period used for this request.
Mostrar atributos secundarios
period.daysLength of the period in days.
period.fromStart of the period, inclusive.
period.toEnd of the period, exclusive. Daily figures therefore run through the previous whole UTC day and never include the one in progress.
panel_statusFor 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
capturedNumber of eligible campaigns in the first 100 newest panel rows for each tracked domain. This sampled value is independent of the returned page.
promo_rateFraction 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.
truncatedWhether the sampled statistics or returned page omit part of the requested collection. Use next_cursor to determine whether another page is available.
dataCampaigns in this page, in the requested order.
Mostrar atributos secundarios
data.idThe identifier for this campaign. Use it to fetch this one campaign on its own.
It is a string, and it needs to stay one. The values are long enough that JavaScript, and any other language that stores every number as a floating point value, will round them, and a rounded identifier matches no campaign at all. Compare it and pass it back as text.
data.subjectThe subject line the panel saw on this campaign.
data.sent_atWhen the panel first saw this campaign arrive.
data.image_urlWhere the panel's capture of the rendered email can be fetched, null when it captured none. Panels image only some of what they observe, so an absent creative is an ordinary outcome rather than a failed one. The image is served from the panel's own host rather than from ours, so a page embedding it has to allow that host.
data.reachEstimated recipients this campaign reached, null when the panel observed the campaign but published no estimate for it.
data.read_rateEstimated share of recipients who read this campaign, null when the panel published no rate for it. Panel read rates count dwell time, so they do not move with the automatic opens that inflate a sender's own open rate.
data.has_creativeWhether the panel captured the rendered email for this campaign.
data.discount_percentThe first recognized percentage-discount offer in the subject, null when none is recognized. Does not detect dollar discounts, free shipping or offers revealed only inside the email.
data.inbox_rateShare of this campaign that reached an inbox, null when the panel observed it without recording where it landed. It describes this send rather than the brand's domain, so a single bad campaign is visible against a brand whose overall placement still looks healthy.
data.spam_rateShare of this campaign that was filed as spam, null on the same terms.
next_cursorCursor for the next page. Pass back as starting_after to advance forward. null when no next page exists.
prev_cursorCursor for the previous page. Pass back as ending_before to step backward. null when no previous page exists.
refresh_cursorRefresh 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.
Recursos relacionados
Continúa con la documentación, guías y ejemplos de este tema.