If you want a competitor’s Facebook ads in a spreadsheet, the official Facebook Ad Library API won’t get you there. Meta’s ads_archive endpoint returns ads about politics, elections and social issues from any country, and other ads only if they were shown in the EU. A brand’s ad that runs only in the US never comes back, even while you can watch it run on facebook.com/ads/library.
For the ads it does return, you get the text, the dates and a link to a snapshot page, but no image or video URLs, landing page or button. And you need to pass Meta’s identity check before you get a token.
The public Ad Library shows every running ad in every country, creative and landing page included. LurkAPI returns it as JSON for 1 credit a call, with an API key and no approval step. In the US a stopped ad disappears from the Library, so save ads while they run.
Meta’s API covers political ads and ads shown in the EU
The EU slice exists because the Digital Services Act makes large platforms keep a searchable archive of every ad they show there. Meta keeps political ads for seven years, running or stopped, and EU ads for a year after their last impression (Meta’s Transparency Center). Every other ad shows in the public Library only while it runs, and never comes back from the API.
Other ads, such as a US-only campaign
Meta’s APINot returned
Public Ad Library (LurkAPI)Every running ad, creative included
Political and EU ads: both return them, with the same retention.
Meta’s API never returns the creative. No field holds an image, a video, the landing page or the button, only ad_snapshot_url, a page that displays the ad.
To get a token you confirm your identity, which can take a few weeks. A long-lived token lasts about 60 days, so a job that runs for months has to renew it.
Every ad from one brand: find the page, then follow the cursor
Start with the page id. Brands run several pages, and other pages borrow the name: search companies for “gymshark” returns 15 pages, three of them verified (Gymshark, GymsharkDE and GymsharkFRA) and others like “Gymshark Sale HQ” with 0 likes. Pick by verification and likes. Regional pages run their own ads, so a whole brand can mean one loop per page.
Then ask for that page’s ads with company ads:
URL=https://api.lurkapi.com/v1/facebook
curl -G "$URL/adLibrary/company/ads" \
-d pageId=129669023798560 \
-d country=US \
-d status=active \
-H "x-api-key: $LURK_KEY"Gymshark
page_name
Running since Sep 30, 2026
is_active, start_date
Just dropped: Whitney Leggings 🔥
snapshot.title
New shades. New mid-rise waistbands. New mini flared-leg designs. Same ButterSmooth™ fabric. Now live 😍
snapshot.body.text
Shop nowgymshark.com/collections/new-releases/womens
cta_text, link_url
snapshot.cards
publisher_platform
display_format, collation_count
The same ad in the JSON response (trimmed)
{
"success": true,
"credits_charged": 1,
"page_id": "129669023798560",
"page_name": "Gymshark",
"results": [
{
"ad_archive_id": "1993572327992819",
"is_active": true,
"start_date": 1790751600,
"end_date": 1791529200,
"publisher_platform": ["FACEBOOK", "INSTAGRAM", "AUDIENCE_NETWORK", "THREADS"],
"collation_count": 5,
"snapshot": {
"body": {
"text": "New shades. New mid-rise waistbands. New mini flared-leg designs. Same ButterSmooth™ fabric. Now live 😍"
},
"title": "Just dropped: Whitney Leggings 🔥",
"link_url": "https://www.gymshark.com/collections/new-releases/womens",
"cta_text": "Shop now",
"cta_type": "SHOP_NOW",
"display_format": "DCO",
"cards": [
{
"video_sd_url": "https://video-bos5-1.xx.fbcdn.net/o1/v/t2/f2/m412/AQMGjBK1yf…",
"video_preview_image_url": "https://scontent-bos5-1.xx.fbcdn.net/v/t39.35426-6/831641564…"
}
]
}
}
],
"searchResultsCount": 698,
"cursor": "AQHTLssVzkZtEDW-…"
}The first call returns up to 30 ads and searchResultsCount, Facebook’s estimate of the total. After that, pass cursor back: each call returns up to 10 more, and cursor comes back null on the last page. The count is only an estimate, so loop until the cursor runs out rather than to the count.
Gymshark’s 699 running US ads took 69 calls: 69 credits, $0.03 to $0.06 at pack prices, which fits inside the 100 free credits you get at signup.
every_ad.py: the same loop in Python
import json
import os
import requests
URL = (
"https://api.lurkapi.com"
"/v1/facebook/adLibrary"
"/company/ads"
)
# Gymshark, running ads, US
PARAMS = {
"pageId": "129669023798560",
"country": "US",
"status": "active",
}
KEY = os.environ["LURK_KEY"]
HEADERS = {"x-api-key": KEY}
# 30 + 99 × 10 = 1,020 ads at most
MAX_PAGES = int(
os.environ.get("MAX_PAGES", "100")
)
ads, cursor = [], None
for _ in range(MAX_PAGES):
params = dict(PARAMS)
if cursor:
params["cursor"] = cursor
page = requests.get(
URL,
params=params,
headers=HEADERS,
timeout=90,
).json()
if not page["success"]:
raise SystemExit(page["error"])
ads += page["results"]
cursor = page["cursor"]
if not cursor:
break
OUT = "gymshark-us.jsonl"
with open(OUT, "w") as f:
for ad in ads:
line = json.dumps(ad)
f.write(line + "\n")
ids = {a["ad_archive_id"] for a in ads}
print(len(ads), "ads")
print(len(ids), "unique")
print("more pages:", bool(cursor))In the US, a stopped ad is gone, so save ads while they run
The public Library keeps stopped ads only where a rule makes it: political ads and ads shown in the EU or the UK. A US-only ad drops out of the Library when it stops.
On the day Gymshark had 698 ads running in the US, the Library still listed just 6 of its stopped US ads. In the UK it listed 20,278 stopped ones, going back to ads that ended in October 2025.
To keep a US competitor’s ad history, run the loop daily and save every ad. Daily is enough: the Library’s dates are whole days. Save the images and videos on the same run, because the media links in each response expire about four days after you fetch them.
Read the run length, the versions and the cards
On a running ad, end_date is its latest day of delivery, so end_date minus start_date is how long it has been live. Brands keep paying for ads that sell, so run length is a good first filter: on Gymshark’s first US page, most ads were about a week old and four catalog ads had been running for 99 days. There’s more on reading that in how long Facebook ads run.
collation_count is how many near-identical versions are grouped under one entry. A high number means the brand is scaling the ad: “The NEW hybrid collection” had 11.
Dynamic ads keep their copy in snapshot.cards. The DCO and DPA formats made up 25 of Gymshark’s first 30 US ads, and on some of them the top-level title reads {{product.name}}. Read the cards for each version’s headline, text and media.
To look across advertisers instead of one, search the Ad Library by keyword for free, or call search ads. For a single ad by its id or Ad Library link, use ad details.



