The Problem
Meta's official route to Ad Library data is the Ad Library API on graph.facebook.com, and getting to it is the hard part. You need a Meta developer app, an access token, and identity confirmation before the first call. Once you are through, the Content Library and API allow a maximum of 500,000 records per seven-day rolling window, and the Marketing API applies its own tiered rate limits on top. Teams that give up on the official path usually land on a scraping tool priced between $79 and $399 a month, and most of those hand back the first page of results and stop there. The thing you actually wanted -- every ad a competitor is running, not the first thirty -- is the thing that is hardest to get.
The Scavio Solution
Scavio reads the public Ad Library the way a logged-out browser does, so there is no app, no access token, and no review step. POST a query and the first page returns thirty ads. The response carries a next_cursor, and walking it pages through the rest of the result set roughly ten ads at a time until has_next_page goes false. The cursor is self-contained, so paging is stateless and you can stop and resume without holding a session. Measured on 2026-08-12: a search for a national advertiser returned thirty ads in 5.2 seconds for one credit. Nothing in this path touches a login or the token-gated ads_archive API -- it is the same public data Meta serves to any visitor, returned as JSON instead of HTML.
Before
Before Scavio, pulling a competitor's full ad history meant either shipping a Meta app through review for a token you would then have to keep alive, or paying a monthly subscription for a tool that showed you page one and called it coverage.
After
After Scavio, an API key you already have walks an advertiser's entire library in a loop you can read in one sitting. Depth costs credits rather than a procurement cycle.
Who It Is For
Competitive intelligence teams, performance marketers, and anyone building ad-monitoring tooling who has hit either the token wall or the first-page ceiling. If you have ever written 'why does this only return 30 results' in a ticket, this is the page for you.
Key Benefits
- No access token, no Meta developer app, no review queue
- Full cursor pagination -- walk an advertiser's whole library, not just page one
- Thirty ads on the first page in about five seconds, one credit
- Stateless cursors, so paging can stop and resume
- Search the library, list one Page's ads, or open a single ad by archive id
- Same key covers 31 platforms, including Google Ads Transparency
Python Example
import os, requests
API = "https://api.scavio.dev/api/v1/meta-ads/search"
HEADERS = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}"}
def walk_ad_library(query: str, country: str = "US", max_pages: int = 10):
"""Page an advertiser's ads until Meta stops handing back a cursor.
Page 1 returns 30 ads; each page after that returns about 10, so depth
costs credits -- max_pages is the budget knob, not a safety rail.
"""
cursor, page, ads = None, 0, []
while page < max_pages:
body = {"query": query, "country": country}
if cursor:
body["cursor"] = cursor
r = requests.post(API, headers=HEADERS, json=body, timeout=60)
r.raise_for_status()
data = r.json()["data"]
ads.extend(data.get("ads", []))
page += 1
if not data.get("has_next_page"):
break
cursor = data.get("next_cursor")
return ads
ads = walk_ad_library("nike")
print(f"{len(ads)} ads")
# total_results is capped: Meta reports ">50,000", never an exact figure.
# Political and issue ads carry spend, reach and impressions; commercial ads
# leave those null. That is Meta's disclosure rule, not a gap in the response.
for ad in ads[:3]:
print(ad["ad_archive_id"], ad.get("cta_text"), ad.get("byline"))
JavaScript Example
const API = "https://api.scavio.dev/api/v1/meta-ads/search";
const headers = {
Authorization: `Bearer ${process.env.SCAVIO_API_KEY}`,
"content-type": "application/json",
};
// Page 1 returns 30 ads; later pages about 10 each, so depth costs credits.
async function walkAdLibrary(query, { country = "US", maxPages = 10 } = {}) {
const ads = [];
let cursor = null;
for (let page = 0; page < maxPages; page++) {
const body = { query, country, ...(cursor ? { cursor } : {}) };
const res = await fetch(API, {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`meta-ads ${res.status}`);
const { data } = await res.json();
ads.push(...(data.ads ?? []));
if (!data.has_next_page) break;
cursor = data.next_cursor;
}
return ads;
}
const ads = await walkAdLibrary("nike");
console.log(`${ads.length} ads`);
// total_results caps at 50,000 -- Meta only ever reports ">50,000".
Platforms Used
Meta Ad Library
Public Meta Ad Library creatives with full cursor pagination, no token