LurkAPI

Meta Ad Library API: what it returns and how to get every ad

The Facebook Ad Library API only returns political ads and ads shown in the EU, with no images. Get every ad a brand runs from the public Ad Library instead.

5 min read

A gray card-catalog cabinet with two drawers pulled open and full of index cards, while every other drawer is shut with an amber padlock.

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.

The two routes differ on one kind of ad: anything that isn’t political and never reached the EU.

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:

Gymshark’s running US ads · 1 credit
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

2 cards, each with an SD and an HD video URL

snapshot.cards

Facebook, Instagram, Audience Network, Threads

publisher_platform

DCO (dynamic creative), 5 versions

display_format, collation_count

One of the 30 ads on the first page, as the response holds it. Field names in gray.
The same ad in the JSON response (trimmed)
Ad 15 of 30 · trimmed, 1 of 2 cards
{
  "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 running US ads, one call at a time: 30 on the first call, then up to 10 per cursor call, until the cursor came back null on call 69 with 699 ads.

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
every_ad.py · Python, requests
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.

Gymshark in the public Ad Library: a US ad that stops drops out, so with 698 running only 6 stopped US ads were still listed. In the UK, 20,278 stopped ads were still listed, some 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.

Questions

Is the Facebook Ad Library API free?

Meta lists no price. The cost is getting in: an identity check that can take a few weeks, a developer account, and an access token you renew about every 60 days.

Does the Facebook Ad Library API include commercial ads?

Only ads shown in the EU. Everywhere else it returns political and issue ads only, so a brand’s US-only ads never come back.

Does the Facebook Ad Library API return images and videos?

No. Each ad has an ad_snapshot_url, a page that displays the creative, and no field holds an image or video URL.

What is the Facebook Ad Library API rate limit?

Meta publishes no number for ads_archive. Going over returns error 613, “Calls to this api have exceeded the rate limit.”

How long does the Facebook Ad Library keep ads?

Political and issue ads for seven years, ads shown in the EU until a year after their last impression, and other ads only while they run.

Keep reading