Brands & Brand tracker
Track Meta pages, pull brand analytics, and manage digest notification prefs — all with X-API-Key. MCP equivalents: lijst_met_gevolgde_merken, track_merk, analyseer_gevolgd_merk.
List tracked brands
/api/v1/brandsPaginated list of tracked brands for the API key owner.
X-API-Key on every request.Query parameters
sorterenstringSort field.
dirstringSort direction.
zoekenstringMatch on brand name or page id.
board_idstringOnly brands on that saved board.
paginainteger1-based page.
limitintegerPage size; max 50.
litestringSet to 1 for lighter enrichment.
filter_active_adsstringfilter_trafficstringfilter_revenuestringrevenue_periodstring
curl -sS \
-H "X-API-Key: $WH_API_KEY" \
"{origin}/api/v1/brands?sort=date_added&dir=desc&search=string&board_id=string&page=1&limit=10&lite=string&filter_active_ads=string&filter_traffic=string&filter_revenue=string&revenue_period=30d"const res = await fetch(`${ORIGIN}/api/v1/brands?sort=date_added&dir=desc&search=string&board_id=string&page=1&limit=10&lite=string&filter_active_ads=string&filter_traffic=string&filter_revenue=string&revenue_period=30d`, {
method: 'GET',
headers: {
'X-API-Key': process.env.WH_API_KEY,
},
});
const data = await res.json();import os, requests
res = requests.get(
f"{ORIGIN}/api/v1/brands?sort=date_added&dir=desc&search=string&board_id=string&page=1&limit=10&lite=string&filter_active_ads=string&filter_traffic=string&filter_revenue=string&revenue_period=30d",
headers={"X-API-Key": os.environ["WH_API_KEY"]},
)
data = res.json(){
"brands": [
{
"id": "108897621643793",
"name": "Allbirds",
"logo_url": "https://…",
"page_url": "https://www.facebook.com/108897621643793",
"status": "active",
"total_ads": 412,
"active_ads_on_page": 142,
"formats": {
"videos": 84,
"images": 52,
"dco": 4,
"carousels": 2
},
"new_ads_count": 18,
"new_ads_change": 0.14,
"traffic": 412000,
"traffic_change": 0.08,
"commerce": null,
"added_at": "April 12, 2024",
"primary_domain": "allbirds.com",
"source_shopid": "allbirds",
"details_url": "/store/allbirds/meta-ads"
}
],
"total": 37,
"page": 1,
"limit": 10,
"total_pages": 4,
"brand_request_quota": {
"used": 2,
"limit": 30,
"remaining": 28,
"month": "2026-08"
}
}{
"success": false,
"error": "Unauthorized"
}{
"success": false,
"error": "WinningHunter Basic plan or higher required"
}{
"success": false,
"error": "Rate limit exceeded. Max 60 requests per minute."
}Response is an object with a brands array — not a bare array and not { success, data }.
Brand ads
/api/v1/brands/adsPaginated Meta ads for a tracked brand page.
X-API-Key on every request.Query parameters
idstringrequiredFacebook page_id from the brands list.
paginainteger0-based page index.
date_rangestringall | live | 7d | 30d | 3m | 6m | custom (with date_from + date_to).
date_fromstringY-m-d when date_range=custom.
date_tostringY-m-d when date_range=custom.
curl -sS \
-H "X-API-Key: $WH_API_KEY" \
"{origin}/api/v1/brands/ads?id=108897621643793&page=0&date_range=string&date_from=string&date_to=string"const res = await fetch(`${ORIGIN}/api/v1/brands/ads?id=108897621643793&page=0&date_range=string&date_from=string&date_to=string`, {
method: 'GET',
headers: {
'X-API-Key': process.env.WH_API_KEY,
},
});
const data = await res.json();import os, requests
res = requests.get(
f"{ORIGIN}/api/v1/brands/ads?id=108897621643793&page=0&date_range=string&date_from=string&date_to=string",
headers={"X-API-Key": os.environ["WH_API_KEY"]},
)
data = res.json()[
{
"productid": "1284756102394857",
"pageName": "Allbirds",
"page_id": "108897621643793",
"caption": "Meet the Tree Runner — carbon neutral comfort.",
"copy": "Meet the Tree Runner — carbon neutral comfort.",
"started": "2025-11-02",
"lastSeen": "2026-03-14",
"daysrunning": 133,
"running_days": 133,
"adscore": "Winning",
"adscore_reasons": [
"Ad is running for longer than 7 days"
],
"total_eu_views_30d": 842000,
"urlStore": "https://allbirds.com/products/mens-tree-runners",
"saved": false,
"hidden": false
}
]{
"error": "Page ID is required"
}{
"success": false,
"error": "Unauthorized"
}Returns a JSON array of processed ad objects (same Utils::process_ads shape as Meta ad library / Magic AI) — not { success, data }. Use daysrunning / pageName (and productid). There is no bare id of platform field on these cards.
Track / untrack
/api/v1/brands/followAdd a Meta page to Brand Tracker from a Facebook Ad Library URL.
X-API-Key on every request.Body parameters
urlstringrequired
curl -sS \
-X POST \
-H "X-API-Key: $WH_API_KEY" \
-H "Content-Type: application/json" \
"{origin}/api/v1/brands/follow" \
-d '{"url":"https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&view_all_page_id=108897621643793"}'const res = await fetch(`${ORIGIN}/api/v1/brands/follow`, {
method: 'POST',
headers: {
'X-API-Key': process.env.WH_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&view_all_page_id=108897621643793"
}),
});
const data = await res.json();import os, requests
res = requests.post(
f"{ORIGIN}/api/v1/brands/follow",
headers={"X-API-Key": os.environ["WH_API_KEY"]},
json={
"url": "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&view_all_page_id=108897621643793"
},
)
data = res.json(){
"success": true,
"brand": {
"id": "108897621643793",
"name": "Allbirds",
"logo_url": "https://…",
"status": "active",
"formats": {
"videos": 0,
"images": 0,
"dco": 0,
"carousels": 0
}
},
"has_data": true,
"details_url": "/store/allbirds/meta-ads",
"amount": 38,
"max_allowed": 50
}{
"success": false,
"error": "Invalid URL",
"message": "Please enter a valid Facebook Ad Library URL with a view_all_page_id parameter."
}{
"success": false,
"error": "Unauthorized"
}{
"success": false,
"error": "Brand limit reached",
"limit": "reached",
"message": "You have reached your plan limit of 50 competitors tracked. Upgrade to follow more."
}/api/v1/brands/unfollowRemove one or more tracked brands.
X-API-Key on every request.Body parameters
pagina-idstringpage_idsarrayArray of page ids.
curl -sS \
-X POST \
-H "X-API-Key: $WH_API_KEY" \
-H "Content-Type: application/json" \
"{origin}/api/v1/brands/unfollow" \
-d '{"page_id":"string","page_ids":[]}'const res = await fetch(`${ORIGIN}/api/v1/brands/unfollow`, {
method: 'POST',
headers: {
'X-API-Key': process.env.WH_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"page_id": "string",
"page_ids": []
}),
});
const data = await res.json();import os, requests
res = requests.post(
f"{ORIGIN}/api/v1/brands/unfollow",
headers={"X-API-Key": os.environ["WH_API_KEY"]},
json={
"page_id": "string",
"page_ids": []
},
)
data = res.json(){
"success": true,
"amount": 37,
"max_allowed": 50,
"removed": 1
}{
"success": false,
"error": "Invalid page id"
}{
"success": false,
"error": "Unauthorized"
}Also available: POST /api/v1/brands/follow-by-domain (body: { "domain": "allbirds.com" }), POST /api/v1/brands/request (request a brand not yet in the index).
If the page is already tracked, follow returns { "success": true, "already_tracked": true, "has_data": bool, "details_url": "…", "message": "Brand is already in your tracker." } instead of the brand object.
Brand analytics tabs
All require id (page_id) unless noted. GET /api/v1/brands/fetch-status is the exception — it requires pagina-id (not id). Optional date_range / date_from / date_to — same rules as Time windows.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/brands/amount-tracked |
Tracked count + plan cap — { "amount": int, "max_allowed": int } |
| GET | /api/v1/brands/overview-cards |
Summary KPI cards |
| GET | /api/v1/brands/top-ads |
Top ads (platform, sorteren query params) |
| GET | /api/v1/brands/top-ads-platform-counts |
Ad counts by platform |
| GET | /api/v1/brands/ad-rank-leaderboard |
Rank leaderboard |
| GET | /api/v1/brands/ads-launched |
Ads launched over time |
| GET | /api/v1/brands/live-ads-over-time |
Live ad count series |
| GET | /api/v1/brands/first-ad-date |
First seen ad date |
| GET | /api/v1/brands/ad-copies |
Ad copy snippets |
| GET | /api/v1/brands/ad-headlines |
Headlines |
| GET | /api/v1/brands/ad-hooks |
Hooks |
| GET | /api/v1/brands/commerce |
Commerce signals |
| GET | /api/v1/brands/ads-by-ids |
Batch fetch by ad ids |
| GET | /api/v1/brands/personas |
Persona breakdown |
| GET | /api/v1/brands/themes |
Creative themes |
| GET | /api/v1/brands/angles |
Hoeken |
| GET | /api/v1/brands/desires |
Verlangens |
| GET | /api/v1/brands/emotions |
Emoties |
| GET | /api/v1/brands/awareness-stages |
Awareness stages |
| GET | /api/v1/brands/funnel-stages |
Funnel stages |
| GET | /api/v1/brands/usps |
Unieke verkoopargumenten |
| GET | /api/v1/brands/partner-pages |
Partner pages |
| GET | /api/v1/brands/partnership-ads |
Partnership ads |
| GET | /api/v1/brands/associated-domains |
Gerelateerde domeinen |
| GET | /api/v1/brands/landing-pages |
Landing pages |
| GET | /api/v1/brands/fetch-status |
Data readiness — query pagina-id → { success, has_data, summary } |
Each costs 1 API credit and returns the same JSON as the dashboard tab.
/api/v1/brands/overview-cardsSummary KPI cards (personas, themes, angles, desires, emotions, USPs).
X-API-Key on every request.Query parameters
idstringrequiredFacebook page_id.
curl -sS \
-H "X-API-Key: $WH_API_KEY" \
"{origin}/api/v1/brands/overview-cards?id=string"const res = await fetch(`${ORIGIN}/api/v1/brands/overview-cards?id=string`, {
method: 'GET',
headers: {
'X-API-Key': process.env.WH_API_KEY,
},
});
const data = await res.json();import os, requests
res = requests.get(
f"{ORIGIN}/api/v1/brands/overview-cards?id=string",
headers={"X-API-Key": os.environ["WH_API_KEY"]},
)
data = res.json(){
"success": true,
"personas": [
{
"tag": "Health-conscious millennial",
"count": 42
}
],
"themes": [
{
"tag": "Sustainability",
"count": 28
}
],
"angles": [
{
"tag": "Comfort-first",
"count": 19
}
],
"desires": [
{
"tag": "Feel good",
"count": 15
}
],
"emotions": [
{
"tag": "Trust",
"count": 12
}
],
"usps": [
{
"tag": "Carbon neutral",
"count": 9
}
],
"personas_fetching": false,
"themes_fetching": false,
"angles_fetching": false,
"desires_fetching": false,
"emotions_fetching": false,
"usps_fetching": false
}/api/v1/brands/ad-copiesPaginated ad copy snippets for insight tab.
X-API-Key on every request.Query parameters
idstringrequiredpaginaintegersorterenstringzoekenstring
curl -sS \
-H "X-API-Key: $WH_API_KEY" \
"{origin}/api/v1/brands/ad-copies?id=string&page=1&sort=Usage&search=string"const res = await fetch(`${ORIGIN}/api/v1/brands/ad-copies?id=string&page=1&sort=Usage&search=string`, {
method: 'GET',
headers: {
'X-API-Key': process.env.WH_API_KEY,
},
});
const data = await res.json();import os, requests
res = requests.get(
f"{ORIGIN}/api/v1/brands/ad-copies?id=string&page=1&sort=Usage&search=string",
headers={"X-API-Key": os.environ["WH_API_KEY"]},
)
data = res.json(){
"success": true,
"data": [
{
"copy": "Meet the Tree Runner — carbon neutral comfort.",
"count": 14,
"ad_ids": [
"1284756102394857"
],
"max_days_running": 133,
"max_days_running_ad_id": "1284756102394857"
}
],
"has_more": true
}Other insight tabs (ad-headlines, hooks, personas, …) follow the same { success, data[], has_more } pattern unless noted in the dashboard.
Brand boards & folders
Organize tracked brands into folders and boards — same as the Brand Tracker sidebar. All routes cost 1 API credit and accept JSON body or form fields (query params are merged for GET).
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/brands/folders-with-boards |
Nested folders + boards tree |
| POST | /api/v1/brands/create-folder |
Create folder (naam) → { success, folder_id, name } |
| POST | /api/v1/brands/rename-folder |
Rename folder (id, naam) |
| POST | /api/v1/brands/delete-folder |
Delete folder (id) |
| POST | /api/v1/brands/create-board |
Create board (folder_id, naam) → { success, board_id, name } |
| POST | /api/v1/brands/rename-board |
Rename board (id, naam) |
| POST | /api/v1/brands/delete-board |
Delete board (id) |
| POST | /api/v1/brands/add-to-board |
Add brand to board (board_id, brand id fields) |
| POST | /api/v1/brands/remove-from-board |
Remove brand from board |
| POST | /api/v1/brands/generate-shareable-board-link |
Share link → { success, url, token } |
POST bodies may be form fields or JSON (JSON is merged into $_POST on /api/v1/brands/*). Board list is a bare array: [{ "id", "name", "created_at", "boards": [{ "id", "name", "brands_count", "brands": [] }] }].
Brand digest notifications
/api/v1/brands/notifications/prefsBrand digest email preferences and per-brand toggles.
X-API-Key on every request.curl -sS \
-H "X-API-Key: $WH_API_KEY" \
"{origin}/api/v1/brands/notifications/prefs"const res = await fetch(`${ORIGIN}/api/v1/brands/notifications/prefs`, {
method: 'GET',
headers: {
'X-API-Key': process.env.WH_API_KEY,
},
});
const data = await res.json();import os, requests
res = requests.get(
f"{ORIGIN}/api/v1/brands/notifications/prefs",
headers={"X-API-Key": os.environ["WH_API_KEY"]},
)
data = res.json(){
"success": true,
"prefs": {
"auto_enable_new_brands": true,
"channels": {
"email": true,
"in_app": false
},
"digest": {
"timezone": "UTC",
"daily": {
"enabled": false,
"time": "09:00"
},
"weekly": {
"enabled": false,
"time": "09:00",
"weekday": 1
},
"monthly": {
"enabled": false,
"time": "09:00",
"monthday": 1
}
},
"events": {
"new_meta_ads": {
"in_digest": true
},
"new_emails": {
"in_digest": true
},
"new_products": {
"in_digest": true
},
"active_ads_change": {
"in_digest": true
},
"spend_eu": {
"in_digest": true,
"threshold_pct": 50
},
"ad_rank": {
"in_digest": true,
"top_n": 5
}
},
"brands": {
"108897621643793": {
"enabled": true
}
}
},
"brands": [
{
"id": "108897621643793",
"name": "Allbirds",
"logo_url": "https://…",
"enabled": true
}
],
"enabled_count": 12,
"total_count": 37
}{
"success": false,
"error": "Unauthorized"
}POST /api/v1/brands/notifications/prefs/save — JSON body with nested channels, digest, events, auto_enable_new_brands, and/or per-brand toggles via brands map or brand_updates. Returns { "success": true, "prefs": { …full prefs object… } }.
Product inbox notifications (/api/v1/notifications/*) are documented on Notifications.
Date range on insight tabs
date_range |
Betekenis |
|---|---|
(omitted) of alle |
No date filter |
live |
Last seen in the last 4 days (updated_at) |
7d, 30d, 3m, 6m |
Rolling start-date cutoff |
custom |
Requires date_from + date_to (Y-m-d) |