# Changelog of Meta Ad Library Scraper: Facebook & Instagram Ads (`humble-echidna/meta-ad-library`) Actor

- **URL**: https://apify.com/humble-echidna/meta-ad-library/changelog.md
- **Full Actor documentation**: https://apify.com/humble-echidna/meta-ad-library.md

## Changelog

Versions follow MAJOR.MINOR.PATCH (`src/version.py`); Apify shows MAJOR.MINOR from `.actor/actor.json`.
Every run logs its version and records it in the `RUN_STATS` key-value record.

### 0.1.5 (2026-10-23)

- Fixed: a search Meta kept answering with ads it had already sent, always with a next page to come, was paged until
  the run timed out (onlyNew off). Any search now stops after 3 pages in a row with no ad it hadn't returned yet, as
  onlyNew searches already did, and the run ends SUCCEEDED with what it found.
- No change to output fields or price.

### 0.1.4 (2026-10-16)

- A run that returns nothing (every input bad, private, blocked or unreachable, or an empty input) now ends
  SUCCEEDED with an empty dataset and the reason as its status message and in the log, instead of FAILED. Only an
  unexpected error still fails the run. `RUN_STATS` keeps the reason as before.
- No change to output fields or price.

### 0.1.3 (2026-10-15)

- Fixed: a Page address written without the periods of its username (facebook.com/HelloFreshDE for
  facebook.com/HelloFresh.de) wasn't matched, although Facebook treats both as the same Page. Usernames are now compared
  the way Facebook does: case and periods don't count.
- Fixed: Page and Ad Library links from a localized Facebook (`de-de.facebook.com`, `mbasic.facebook.com`) and Page
  links in Facebook's `/p/Name-<id>/` form were refused as "not a Facebook Page address".
- Fixed: country `UK` (and anything Meta doesn't know, like `EU`) failed every search with "Meta has probably changed
  the Ad Library". `UK` is now searched as `GB` (`EL` as `GR`); other unknown codes fail before any request, saying
  what to give instead; and Meta's answer to a value it can't read says to check the country and dates.
- A run where every search fails now shows the reason in its status message, not just "see log".

### 0.1.2 (2026-10-14)

- Fixed: spend, impressions, reach estimates and button labels came back in the language of the proxy IP's country
  (Indonesian `US$500 rb - US$600 rb`, `Pelajari selengkapnya`), because logged out the Ad Library answers in the
  visitor's local language and runs go out through residential IPs anywhere. Every query now asks for US English
  (`locale=en_US` and an `Accept-Language` header); checked from Indonesian, German, Japanese and Brazilian IPs.
- New fields `spendMin`/`spendMax`, `impressionsMin`/`impressionsMax`, `reachEstimateMin`/`reachEstimateMax`: the
  ranges as numbers (`>1M` is 1000000 and no max, `<1K` is 0 and 1000). Text in any other format gives no number,
  never a wrong one.
- New dataset view "Spend and impressions".

### 0.1.1 (2026-10-14)

- A Page address whose username changed (facebook.com/allbirds is now /weareallbirds) is matched by the Page's exact
  name when exactly one advertiser has it; two Pages with that name fail the line, listing both IDs. `RUN_STATS`
  says how each Page was matched (`pageMatchedBy`: address or name) and its `pageId`.

### 0.1.0 (2026-10-14)

First release.

- Ads from Meta's Ad Library (facebook.com/ads/library) by search term, advertiser Page (address, Page ID or Ad
  Library page address) or pasted Ad Library URL, including single-ad links. Filters: country, active status, ad
  category, media type, platforms, words in any order or exact phrase, shown-from/until dates. A pasted URL keeps its
  own filters; the input fills in the rest.
- One flat row per ad: text, headline, button, link, image and video URLs (carousel, dynamic-creative and catalogue
  cards included), start and end day, days running, platforms, format, versions, special categories, and spend,
  impressions and reach estimate where Meta shows them (political and issue ads). Catalogue templates
  ({{product.name}}) give way to the first card's real text.
- Ad details (optional, no extra charge): EU reach in total and by country, age and gender, EU targeting, payer and
  beneficiary, advertiser Page and Instagram figures.
- Only new ads: remembered per input line and filter set in the user's `meta-ad-library-seen` store, written only
  after the ads are in the dataset; a search stops after 3 pages in a row with nothing new.
- Source: the Ad Library page's own GraphQL queries, logged out, no cookie, no account. Docs and terms checked
  2026-10-14: the official Ad Library API (developers.facebook.com/docs/graph-api/reference/ads_archive) needs an
  identity-confirmed Facebook account and covers political/issue ads and ads shown in the EU/UK only; facebook.com's
  robots.txt disallows every crawler and Meta's terms on automated collection bind its users (Meta v. Bright Data,
  N.D. Cal. 2024: not logged-out collection of public data). Read under Michael's platform robots.txt exception
  (2026-10-14); the README says so.
- On Apify every search goes through a residential IP (Meta rate-limits Apify's own and datacenter IPs on the first
  request) and moves to a fresh one if Meta rate-limits it.
- Charged per ad returned, through Apify's standard `apify-default-dataset-item` event. "Max ads per search" and
  "Max results per run", and the maximum cost per run, stop the run before the next page is fetched.
- Failure isolation: a failing input line (bad address, unknown Page, a refused query) only affects itself.
