Meta-advertentiebibliotheek
GET /api/v1/adlibrary searches Meta (Facebook / Instagram) ads with your API key. /api/adlibrary is a supported alias. Auth and metering match the API reference. MCP equivalent: zoek_op_Facebook-advertenties (and winnende producten vinden, creatief_inspiratiepakket) — see MCP · tools reference.
GET /api/v1/adlibrary
curl -sS -G -H "X-API-Key: $WH_API_KEY" "{origin}/api/v1/adlibrary" \
--data-urlencode "keyword=skincare" \
--data-urlencode "searchkeyword=adtext" \
--data-urlencode "countries=US" \
--data-urlencode "sorting=reach" \
--data-urlencode "page=0" \
--data-urlencode "min=" --data-urlencode "max=" \
--data-urlencode "from=" --data-urlencode "to=" \
--data-urlencode "mediafilter=" --data-urlencode "scroll=" \
--data-urlencode "scaling=" --data-urlencode "niches=" \
--data-urlencode "fromlastseen=" --data-urlencode "tolastseen=" \
--data-urlencode "activestatus="
{
"data": [
{
"id": "1284756102394857",
"page_name": "Glow Beauty Co.",
"ad_creative_body": "This serum changed my skin in 14 days…",
"ad_snapshot_url": "https://www.facebook.com/ads/library/?id=1284756102394857",
"media_type": "video",
"started": "2024-05-12",
"reach": 184000,
"ad_rank": 3
}
],
"total": 240,
"scroll": "eyJwYWdlIjoxfQ=="
}
The in-app Meta ads views call /api/fb-ads with the same query shape — copy a working request from your browser's network tab and swap the path to /api/v1/adlibrary.
Entry guard (required query keys)
The endpoint only returns its main JSON payload when all of the following keys are present in the query string (empty string is allowed):
zoekwoord, trefwoord, min, landen, from, to, sorteren, mediafilter, pagina, max, scroll, schaalbaarheid, nischen, fromlastseen, tolastseen, activestatus
If any are missing, the response is not the usual list payload (avoid this in clients).
Additionally, the inner gate requires trial set in the query of a logged-in / API-key–resolved session. With a valid X-API-Key, you meet the session requirement automatically. Otherwise the response is:
{"error":"logged_out"}
All query parameters
Everything below is read from the query string (same shape as /api/fb-ads in the app).
isset gate (must be present; empty string is OK)
Same list as Entry guard above:
zoekwoord, trefwoord, min, max, landen, from, to, sorteren, mediafilter, pagina, scroll, schaalbaarheid, nischen, fromlastseen, tolastseen, activestatus.
Strongly recommended on every request
The handler reads website, talen, pagetypefilter, sortdirection, apps, en thema's without going through that isset list. Mirror the dashboard and send at least website=All, languages=All, pagetypefilter= (empty = all page types), sortdirection=desc, apps=All, themes=All so behavior matches the UI and optional keys are always defined.
Snake_case aliases (MCP-parity names)
/api/v1/adlibrary (and the /api/adlibrary alias) also accepts the same snake_case argument names as the MCP zoek_op_Facebook-advertenties tool, normalized server-side to the internal keys below (FacebookAdsFilterSpec::publicQueryAliases()). When both names are sent, the internal name wins. Highlights:
| Alias | Internal key |
|---|---|
land / landen, uitsluitingslanden |
landen, excludeCountries |
niche / nischen |
nischen |
technologie / technologieën |
website |
taal / talen, talen uitsluiten |
talen, excludeLanguages |
thema / thema's, apps, apps uitsluiten |
thema's, apps, excludeApps |
mediatype, paginatype, ad_score, weinig vertoningen, filter_voor_groei_in_ranglijst |
mediafilter, pagetypefilter, adscorefilter, lowimpressions, rankgrowthfilter |
ad_created_from / ad_created_to |
from / to |
last_seen_from / last_seen_to |
fromlastseen / tolastseen |
product_created_from / product_created_to |
product_from / product_to |
page_created_from / page_created_to |
pagefrom / pageto |
min_duplicates / max_duplicates |
min / max |
min_ad_spend / max_ad_spend, ad_spend_timeframe |
minadspend / maxadspend, adspendtimeframe |
min_reach / max_reach, reach_timeframe |
minreach / maxreach, reachtimeframe |
min_monthly_visits / max_monthly_visits |
mintraffic / maxtraffic |
min_days_running / max_days_running |
mindays / maxdays |
min_active_ads / max_active_ads |
minactiveads / maxactiveads |
min_active_ads_growth / max_active_ads_growth, active_ads_growth_period |
minactiveadsgrowth / maxactiveadsgrowth, activeadsgrowthperiod |
min_reach_growth / max_reach_growth, reach_growth_period |
minreachgrowth / maxreachgrowth, reachgrowthperiod |
min_ad_rank / max_ad_rank |
minadrank / maxadrank |
min_page_likes / max_page_likes, min_price / max_price, min_copy_length / max_copy_length, min_video_length / max_video_length, min_products_on_store / max_products_on_store |
minpagelikes / maxpagelikes, minprice / maxprice, mincopylength / maxcopylength, minvideolength / maxvideolength, minproductsonstore / maxproductsonstore |
sorteren op / sorteervolgorde |
sorteren / sortdirection |
active_status |
activestatus |
On the API route the entry-guard keys are defaulted server-side (Dashboard::fbAdsPublicBaseQuery()), so a request like ?keyword=skincare&last_seen_from=2026-01-01&last_seen_to=2026-02-01 is valid without sending every isset-gate key. When trefwoord is set and zoekwoord is empty, searchkeyword=All is applied automatically.
Auth / session / trial
| Parameter | Role |
|---|---|
| (none) | With X-API-Key, the request runs as your account (same effect as being logged in). |
trial |
If set (e.g. trial=true), anonymous winning-finder rules apply; combined with filters on page ≠ 0 can return {"message":"need_account"}. |
Pagination, cursor, de-duplication
| Parameter | Notes |
|---|---|
pagina |
0-based page index. |
scroll |
Deep-pagination cursor from search; empty on first request, then copy the scroll value from the previous JSON response. |
nextscrapetime |
Unix seconds; used when sorting=datefound (first page often uses an anchor timestamp from the UI). |
search_token |
Optional [a-zA-Z0-9_-]+; ties ads_per_brand caps to a tab/search in session. |
Keyword search
| Parameter | Allowed / typical values |
|---|---|
trefwoord |
Free text; URL-encoded; lowercased server-side. |
zoekwoord |
'', Alle, landingspagina, paginanaam, advertentietekst, productnaam (must be in this set when trefwoord is non-empty). |
Active ads, scaling, media, page type, adscore, impressions
| Parameter | Values (match the in-app Meta ads filters) |
|---|---|
activestatus |
String waar of onwaar (active-only vs include inactive). |
schaalbaarheid |
'' (none), nodownscaling, upscaling, downscaling. |
mediafilter |
'' / omit effect = all formats; else video's, images, carousel, dco (maps to display_format in ES). |
pagetypefilter |
'' = all; producten, collecties, funnels, nofunnels. |
adscorefilter |
'' = none; winning (Established), schaalbaarheid (Has Potential), testing (Unestablished). Requires the matching in-app tier. |
lowimpressions |
'' = show all; hide = exclude very low impression rows (same behavior as the dashboard filter). |
rankgrowthfilter |
'' = none; comma-separated subset of stijgend, stabiel, afnemend (rank momentum vs historic rank_history; same semantics as the dashboard Groei in rang filter). |
minadrank, maxadrank |
Positive integers — within-brand ad rank (lower = stronger for that brand; #3 / N in the UI). Empty = no bound. Matches the dashboard Advertentiepositie filter. |
Sorting
| Parameter | Values |
|---|---|
sortdirection |
asc of beschrijving (empty defaults to beschrijving). |
sorteren |
'' (None → random order when not shuffling), trending, bedrag per advertentiegroep, laatst gezien, gevonden op, mostrecent (ad start date / started), adspend, langstlopende, bereik, maandelijkse bezoeken, pageactiveads, daysrunning, topklasse (when enabled for your deployment). daysrunning may not apply a dedicated sort branch (behaves like None unless the product changes). |
Spend, reach (with window), traffic, days running, price, copy, video length, active ads on page
| Parameter | Notes |
|---|---|
minadspend, maxadspend |
Integers / numeric strings. maxadspend=999999 is treated as “no upper cap” for the adspend-in-use check. Meaningful adspend filtering may require a higher in-app tier. |
adspendtimeframe |
alle, 7, 30, 90 — window for adspend filter (invalid values → alle). |
minreach, maxreach |
Integers. May require a higher in-app tier. |
reachtimeframe |
Same allowed set as adspendtimeframe. |
mintraffic, maxtraffic |
Store monthly-visits range (absolute). May require a higher in-app tier. |
minstorerevenue, maxstorerevenue |
Est. monthly store revenue range in USD (overlaps store_traffic.monthly_revenue_{min,max} on wlads). Same tier gate as traffic filters. |
storebasedin |
Comma-separated ISO2 shop origin countries → store_traffic.shop_origin_country on wlads (same values as Explore Shops Gevestigd in / stores metadata.country). |
storevisitorcountrymain |
Comma-separated ISO2 — country tied for top visitor share (store_traffic.visitor_country_main). |
storevisitorcountryamong |
Comma-separated ISO2 — country appears in traffic mix (store_traffic.top_countries). |
storevisitorcountryexclude |
Comma-separated ISO2 — exclude ads whose store has traffic from that country. |
trustpilotrating |
Trustpilot stars dash range on store_traffic.trustpilot.ratings (e.g. 4-5, 2,5-3,5). Same tier gate as traffic filters. |
trustpilotreviews |
Trustpilot review-count dash range on store_traffic.trustpilot.total_reviews (e.g. 1000-10.000, 100.000–1.000.000.000). |
storecreated_from, storecreated_to |
Store creation window (Y-m-d) on store_traffic.first_product_created_at. Same tier gate as traffic filters. UI chip param: datestorecreated (Lightpick display range). |
mingrowth, maxgrowth, growthperiod |
Monthly visit growth range, as a signed % change in store traffic (UI values: 25 = 25%). growthperiod selects the window: 1 (default) or 3 months → ranges on store_traffic.monthly_visits_growth_{1,3}m (indexed at 1/100 of the UI %, e.g. 1695 → 16.95). Empty min/max = no bound; negatives select declining stores. Same in-app tier gate as mintraffic. |
minactiveadsgrowth, maxactiveadsgrowth, activeadsgrowthperiod |
Page active ads % growth (dashboard Active ads growth, Page tab). UI min/max are human percent (e.g. 25 = 25%; negatives allowed). activeadsgrowthperiod tokens: 1w, 14d, 1m, 3m (standaard 1m; legacy 7d→1w, 2m→14d, 30d→1m, 90d→3m). Server maps tokens to indexed baselines total_active_ads_on_page_growth_{1w,14d,1m,3m} and filters on ((\texttt{total_active_ads_on_page} - \texttt{baseline}) / \texttt{baseline} \times 100) (baseline is the prior-period active-ad count, not a precomputed pct field). Dashboard labels: 1 week→1w (7d), 2 weeks→14d (14d), 1 month→1m (30d). |
minreachgrowth, maxreachgrowth, reachgrowthperiod |
Ad cumulative EU reach % growth (dashboard Ad EU Reach Growth, EU/UK tab). Same UI % and period semantics as ads growth. Ranges total_eu_views_growth_pct_{period} only (not reach_growth_pct_*, which is page-level reach). Same in-app tier gate as minreach / maxreach. |
mindays, maxdays |
Days-running range filter; may require a higher in-app tier. |
minprice, maxprice |
|
mincopylength, maxcopylength |
|
minvideolength, maxvideolength |
|
minactiveads, maxactiveads |
“Active ads on page” style cap (trial flow may inject minactiveads server-side). |
minpagelikes, maxpagelikes |
Facebook page likes range on indexed page_like_count (ads without the field are excluded when a min is set). |
minigfollowers, maxigfollowers |
Instagram followers range on wlads tracking.igfollowers (denormalized at ingest; no pages-index join). Currently disabled in app (Ads::WLADS_IG_FOLLOWERS_FILTER_ENABLED); enable after field backfill + dashboard UI. |
Data dependency — monthly visit growth.
mingrowth/maxgrowthrange onstore_traffic.monthly_visits_growth_{1,3}mon thewladsindex. The 1-month field is widely populated; 3-month may return no matches until the indexer backfills that scalar.
Data dependency — wlads % growth filters.
minactiveadsgrowthrequirestotal_active_ads_on_pageplustotal_active_ads_on_page_growth_{1w,14d,1m,3m}onwlads(baselines must be prior counts, not deltas).minreachgrowthusestotal_eu_views_growth_pct_{period}when backfilled. Sparse baselines reduce match counts; use broader ranges when testing.
Dates (strings, typically Y-m-d)
| Parameter | Betekenis |
|---|---|
from, to |
Ad creation (started). |
fromlastseen, tolastseen |
Last seen (updated_at). |
product_from, product_to |
Product creation window. |
pagefrom, pageto |
Page creation window. |
Open-ended date ranges are normalized server-side (e.g. only from set → to may be set to match from).
Catalog: countries, stores, niches, languages, apps, themes, excludes
| Parameter | Format |
|---|---|
landen |
Alle or comma-separated ISO 3166-1 alpha-2 codes (e.g. US,GB). |
website |
Alle or comma-separated store codes (e.g. SH = Shopify — see dashboard website filter). |
nischen |
Alle or comma-separated segments (preset codes, /Path/... paths from the niche catalog, or numeric ids — normalized server-side). If the list contains shopping, the server appends product. |
talen |
Alle or comma-separated language codes (as in dashboard #language). |
apps |
Alle or comma-separated numeric app ids (same values as the in-app apps filter). |
thema's |
Alle or comma-separated theme strings (discover via GET /api/themes). |
excludeCountries, excludeWebsites, excludeLanguages, excludeNiches, excludeApps |
Same comma-separated patterns as the include side; niches normalized like nischen. |
Deep links, shuffle, caps
| Parameter | Notes |
|---|---|
pageidfromurl |
Facebook page_id — narrows to one page’s ads. |
productid |
Shopify product id. |
shuffle |
Any non-empty truthy → shuffle mode (random sort; optional last-seen default window). |
ads_per_brand |
1, 2, 3, 5, 10 only — max ads per brand when shuffling / per-brand cap logic runs. |
Validation / errors
Invalid min of max (outside 0…90000000) can yield {"error":"value_not_in_range_1"} of _2, or a non-JSON onwaar body in edge cases.
Discovering valid nischen en thema's (no API key on these routes)
Integrations should learn allowed values from discovery JSON, not from guessing:
| Endpoint | Purpose |
|---|---|
GET /api/niche-counts |
Returns { "niches": [ { "code": "…", "count": N }, … ], "total_with_niche": … } built from the same Meta ads index the product uses. Use the code strings (comma-separated in nischen / excludeNiches) as the ground-truth set that actually appears in ads data. Optional: refresh=1 bypasses disk cache for one request. |
GET /api/themes |
Returns a JSON object with a thema's list (top values + counts, cached ~1h). Optional query: q — if length ≥ 2, substring search for typeahead. Optional: limit (default 200, max 500). Use returned theme names/keys exactly as themes= comma-separated values on /api/adlibrary. |
Preset niche shortcuts (UI “chips”): The app also exposes human-facing two-letter codes (e.g. CG, BY, SP) in the niche filter. Those are valid segments in niches= the same way the browser sends them.
Path / numeric niche segments: Values are resolved using the server’s niche catalog (paths like /Apparel/... and numeric ids map to canonical names in search). If a segment is unknown, it may be passed through as-is — prefer /api/niche-counts over inventing strings.
Opmerking: /api/niche-counts en /api/themes are discovery helpers and do not consume Meta ad library API credits the same way /api/adlibrary does. Your deployment may still require login or other policy in front of these routes.
Feature gates (upgrade JSON)
For accounts without the required product tier, certain filters or sort modes return 200 with a small JSON body (upgrade prompt is in-band, not always HTTP 403):
| Response | Condition (simplified) |
|---|---|
{"upgrade":"standard_adspend"} |
Ad spend filter used, or sorting=adspend |
{"upgrade":"standard_reach"} |
Reach filter or sorting=reach |
{"upgrade":"standard_traffic"} |
Traffic filter |
{"upgrade":"standard_daysrunning"} |
Days-running filter or sorting=daysrunning |
{"upgrade":"standard_adscore"} |
adscorefilter set |
Other notable responses
{"message":"need_account"}— Anonymoustrialrequests beyond allowed usage (trial flow guardrails).- Success payload: JSON object including
data(ads), optionalmessage(human-readable filter hints),total+total_relation(computed on every page for API-key / MCP requests; the dashboard UI fetches totals separately viacount_only=1),scroll(deep-pagination cursor), and free-tier credit fields when applicable.totalcan still benullfor query shapes that post-filter in PHP (e.g.rankgrowthfilter). Each ad may includead_rank(integer position within its brand/page) andrank_history(time series used for momentum / Rank growth filtering in the dashboard). Programmatic and MCP payloads include these fields when present in search results.