Meta's Ad Library is the single best public window into what your competitors are actually spending money on. Every active ad on Facebook and Instagram is there — the creative, the call-to-action, the landing domain, the day it launched. The problem is the interface: it is built for one-off manual lookups, not for tracking fifty advertisers across three markets every week. You cannot diff last month's creative against this month's, you cannot sort by how long an ad has been running, and you certainly cannot pipe it into a dashboard.
This guide shows how to read the same public Ad Library programmatically: full-text ad search with real filters, advertiser profiles, and a strategy-insights endpoint that surfaces launch cadence, format mix, and the winning creatives a brand refuses to turn off. Every call is a single authenticated request that returns clean JSON — no headless browser, no proxy pool, no HTML parsing.
Three endpoints, one job: read a competitor's ad strategy
The Meta Ads surface has three endpoints. Search is your discovery tool; the other two go deep on a single advertiser once you have their page_id. Every response comes back in the standard { data, meta } envelope, where meta carries the exact cost, your remaining balance, and a request_id for support.
| Endpoint | Method | What it returns | Cost |
|---|---|---|---|
/v1/meta_ads/search | POST | Full-text ad search: total + up to 200 ads (creatives, CTA, domain, format, startDate, daysActive, isActive) | $0.0010 |
/v1/meta_ads/advertiser | GET | Page profile: page_name, category, likes, ads_seen, ads_active | $0.0002 |
/v1/meta_ads/insights | GET | Strategy aggregates: format/CTA/platform distributions, launch cadence by month, longest-running active ads | $0.0010 |
You never pay for a miss
Billing only happens on a 200. If an advertiser has no ads or a page_id does not resolve, the 404/5xx costs you nothing — so you can probe aggressively while building a watchlist.
Search: full-text ad discovery with filters
POST /v1/meta_ads/search takes a JSON body — not query params — because there are a lot of optional filters and you rarely use all of them at once. Send a query for full-text keyword search, or scope to a known advertiser, linkDomain, platform, mediaType, or ctaType. The key strategic lever is sortBy: set it to daysActive to float a brand's longest-running (read: best-performing) creatives to the top.
curl -X POST "https://api.virev.ai/v1/meta_ads/search" \
-H "Authorization: Bearer $VIREV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "protein",
"activeOnly": true,
"minDaysActive": 30,
"sortBy": "daysActive",
"limit": 50
}'The same request in JavaScript — the body maps one-to-one to the fields above. Filter by advertiser when you already know who you are watching, or by linkDomain to catch every brand driving traffic to a specific store or funnel.
const res = await fetch("https://api.virev.ai/v1/meta_ads/search", {
method: "POST",
headers: {
"Authorization": "Bearer kb_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
advertiser: "Gymshark",
platform: "instagram",
mediaType: "video",
activeOnly: true,
sortBy: "daysActive",
limit: 100,
}),
});
const { data, meta } = await res.json();
console.log(`${data.total} ads found — charged ${meta.cost}`);
for (const ad of data.ads) {
console.log(ad.pageName, ad.ctaType, ad.linkDomain, `${ad.daysActive}d live`);
}Run it live — edit the query to search your competitor's ads
Longevity is your performance proxy
The Ad Library does not publish spend or impressions for commercial ads, so you cannot read a winner off a metric. Instead, read it off time: a creative a brand has paid to keep live for 90+ days has survived their own optimization loop. Sort by daysActive, set minDaysActive, and the ads at the top are the ones already working.
Advertiser: the cheapest lookup you have
Once search hands you a page_id, GET /v1/meta_ads/advertiser?page_id= gives you the page in profile form: page_name, category, likes, ads_seen, and ads_active. At $0.0002 it is the cheapest call in the catalog — ideal for a nightly sweep that flags when a competitor's ads_active count jumps, a reliable early signal of a new campaign or seasonal push.
import requests
res = requests.get(
"https://api.virev.ai/v1/meta_ads/advertiser",
params={"page_id": "20531316728"},
headers={"Authorization": "Bearer kb_live_..."},
)
payload = res.json()
advertiser = payload["data"]
print(advertiser["page_name"], "-", advertiser["category"])
print("active ads:", advertiser["ads_active"], "/ seen:", advertiser["ads_seen"])
print("charged:", payload["meta"]["cost"])Insights: the whole strategy in one call
GET /v1/meta_ads/insights?page_id= is the report a strategist would spend an afternoon assembling by hand. In one $0.0010 request it aggregates every ad the page has run into: total_ads and active_ads, avg_days_active and max_days_active, a display_format_distribution (video vs. image vs. carousel), a cta_type_distribution, a publisher_platform_distribution (Facebook vs. Instagram vs. Audience Network), ads_started_by_month for launch cadence, and — most importantly — the list of the longest-running active ads.
import requests
res = requests.get(
"https://api.virev.ai/v1/meta_ads/insights",
params={"page_id": "20531316728"},
headers={"Authorization": "Bearer kb_live_..."},
)
insights = res.json()["data"]
print("total ads:", insights["total_ads"], "| active:", insights["active_ads"])
print("format mix:", insights["display_format_distribution"])
print("cta mix:", insights["cta_type_distribution"])
print("launch cadence:", insights["ads_started_by_month"])
# The winning creatives: ads they refuse to turn off
for ad in insights["longest_running_active_ads"]:
print(ad["daysActive"], "days:", ad["ctaType"], "->", ad["linkDomain"])Read those distributions like a media plan. A display_format_distribution skewed 80% to video tells you where a competitor is putting their creative budget. A cta_type_distribution dominated by SHOP_NOW versus SIGN_UP tells you whether they are optimizing for direct sales or list-building. And ads_started_by_month reveals their tempo — a spike in Q4 is a seasonal war chest you can plan around.
A repeatable competitor-tracking loop
Put the three endpoints together and you have a workflow you can run on a schedule instead of clicking through the Ad Library by hand.
- 1
Discover
CallPOST /v1/meta_ads/searchwithadvertiser(or a categoryquery) andsortBy: "daysActive"to pull each competitor's live ads and capture theirpageNameandpage_id. - 2
Snapshot cheaply
RunGET /v1/meta_ads/advertiser?page_id=nightly at $0.0002 each and storeads_active. Alert when the count changes — that is a new campaign going live. - 3
Go deep on movers
When a page's activity jumps, callGET /v1/meta_ads/insights?page_id=for the full format/CTA/platform breakdown and the longest-running creatives to reverse-engineer. - 4
Diff over time
Persist each run and compare weeks. NewlinkDomainvalues mean new funnels; a risingavg_days_activemeans their creatives are getting stickier — and worth studying.
Watch the rate limit on wide sweeps
The default limit is 100 requests/minute, surfaced in the X-RateLimit-* response headers. If you are snapshotting hundreds of advertisers, batch them under that ceiling — a 429 returns a Retry-After header telling you exactly how long to back off (and, like every non-200, costs nothing).
Start tracking competitor ads in one request
Pay $1, get a $5 balance — enough for roughly 5,000 ad searches or 25,000 advertiser lookups. No subscription.
Get your API key →