Search ads
search_adsSearch the spytrend.com ad database and return ad METADATA: title, body_chars (the LENGTH of the ad text in UTF-8 characters), landing domain, status, geos, activity, advertiser, ai_enrichment (ai_category/media_type), media_count and creative-format metadata. The ad TEXT ITSELF is deliberately NOT in list rows — an average body is ~1.1k characters and would bloat a 20-row page 20-80x; fetch the text for a specific ad with get_ad, and use body_chars plus min_body_chars/max_body_chars to find the ads worth opening. The visible /ads Format filter targets creative_formats (video/carousel/single/dynamic); publisher placements are filtered separately via platforms. The downloadable creative media is NOT included here — fetch it with get_media using the returned ids. Filters: keyword (query), country/countries (multi-geo), platforms, categories (ai_category slug), ai_subcategory, status_today (active/inactive/vanished), advertiser_id, webmaster_id, landing_domain, media_type; languages, formats (creative_formats: video/carousel/single/dynamic), cta_buttons, domain_zones, search_in (which field query matches: all/title/advertiser/text), platforms_mode (platforms any/all); point lookups pixel_id / page_id / resolved_ip and contains_in_links (tracking fragments like pixel_id=/sub1=/utm); ranges min_days_active/days_active_to, min_page_likes/max_page_likes, min_body_chars/max_body_chars (ad-text length in characters; both directions cover only ads with known non-empty text), date_from/date_to (Facebook launch date — THE axis for 'new ads in a window': new = launched on Facebook then; first_seen_from/to is the date we first indexed the ad, which also catches late-indexed OLD ads — never use it for market newness); max_countries (with selected countries: additional GEOs outside them; without selected GEO: total GEOs), dedupe (unique creatives only), and saved/folder scopes (saved=all, folder_id). Sort with sort_by + sort_order. Use sort_by=most_reused_creative to rank the selected results by how many ads share the same creative; rows include same_creative_ads_in_selection and other_ads_with_same_creative for the selected filters, plus same_creative_ads_lifetime and advertisers_using_same_creative for the lifetime catalog. creative_match_type=exact identifies the matching contract and creative_reuse_status=exact|unavailable qualifies the counts. For most_reused_creative the absolute number of distinct creative groups is intentionally unavailable/null; page with has_more and next_cursor instead of retrying for a total. To find ads on a domain use landing_domain — NOT query. Cursor pagination via next_cursor; pagination always includes total and total_status (exact/estimated/unavailable), plus counted_at/count_cached for exact analytical totals. Any closed launch OR discovery window of at most 31 calendar days completes a raw exact unique-ad count in-call. A discovery request with exactly one country plus optional AI category/subcategory filters can instead use the canonical daily cube for an exact count across up to 90 inclusive days, or from first_seen_from through today; no other filters may be present on that cube path. Other wider/open combinations use the normal estimated/unavailable contract, and malformed/reversed dates are rejected. An exact count that includes today is exact for ads ingested so far, while analysis.includes_open_utc_day and data_complete_through make the incomplete tail explicit. status_today always means CURRENT state: with a date window, active is the currently-active survivors of that selected cohort, not historical activity on each date. Except for the intentional most_reused_creative group-total omission described above, unavailable means the async count did not settle inside this call: the number is PENDING, not zero and not a measurement; re-issue the identical call in a few seconds to obtain it (analysis.count_note repeats this warning in-band). NEVER present an unavailable count as 0. With categories set, matches and totals cover AI-LABELED ads only — a labeled SUBSET of the market (analysis.category_semantics=ai_labeled_ads_only_not_comparable_to_extrapolated_trends); get_trends aggregates for the same category×geo are extrapolated market estimates and will always be larger — never cross-compare the two. For an advertiser's or webmaster's EXACT ad count (total AND active), do NOT paginate search_ads — call get_advertiser / get_webmaster. To download the creatives, pass the ids to get_media; to SAVE them, pass the ids to add_to_favorites. Respects the caller's plan/verticals. For aggregate rankings use get_trends. MULTILANG UPLOAD: formats=[carousel_multilang] selects the same detected carousel pattern as the website; carousel_multilang_categorized selects its AI-categorized subset. This is independent of the languages filter. Formats combine with OR, so do not combine the base and categorized variants when requesting only categorized results. Search remains 1 token per row; opening a matching ad with get_ad or get_media costs 50 tokens. QUOTA: 1 token per DELIVERED result (default page 20 = 20 tokens; short pages auto-refund). Results carry metadata only; get_ad/get_media charge 50 tokens per multilang-upload ad opening, 1 per ordinary ad, and get_media charges 10 per creative-catalogue item. Each new opening call is billed separately. Meta search_ads rows include is_multilang and opening_price (Spytrend tokens per ad per call); unavailable/null means the price could not be verified, not zero. Opening quotes are not a debit or a price lock. Request small limits and narrow filters. TIKTOK SOURCE: pass source="tiktok" to query the TIKTOK ad corpus instead of Meta. (The platforms filter does NOT do this — platforms are Meta publisher placements.) ⚠️ TIKTOK IS PAID PER ROW at a premium: every DELIVERED TikTok ad row costs 100 tokens and every TikTok webmaster row/card costs 100 tokens (Meta rows cost 1), deducted from the same token balance (paid plans: 40,000/month = up to 400 TikTok rows). Request small limits and narrow filters; you are charged only for rows actually delivered (short pages auto-refund; if the balance covers only part of the request, that part is delivered — a 3-row request on a 150-token balance returns 1 row and charges 100). TikTok requires a Pro-or-higher plan: free/starter callers and unlinked agents get an actionable upgrade refusal, never data and never a charge. TikTok rows have their OWN shape, returned under tiktok_data (verified live 2026-07-30): id/external_id (TikTok ad id), advertiser{id,external_id,name} plus advertiser_id (TikTok BUSINESS id), start_date/end_date (the ad's delivery window) and first_seen_date/last_seen_date (SpyTrend indexing), days_active, is_active + status_today, ai_category/ai_subcategory, countries + targeting_geos + targeting_details (OS/age/gender/regions), audience_size, objective, sponsor (the 'paid for by' funder) and registry_location, landing_domain + link_url, body/title/call_to_action, creative_format, creative_id, creative_ad_count (how many ads reuse that creative), is_blurred/is_cloaked, and media[] with media_type + thumbnail_url on media-tt.spytrend.com. Media comes WITH the row — do NOT call get_media for TikTok ids (it serves Meta entities only). There are no TikTok engagement counters (plays/likes/comments/shares) on this surface. The TikTok feed serves the ARCHIVED TikTok corpus — exactly what the spytrend.com /ads TikTok tab shows — and pagination carries has_more/next_cursor plus total with total_status (exact, estimated or unavailable; unavailable is not zero). With source=tiktok: Supported TikTok filters: query/search_in, country/countries, country_match, categories, ai_subcategory, languages, formats (single|carousel|video), landing_domain, advertiser_id (TikTok business id), webmaster_id, contains_in_links, media_type, cta_buttons, domain_zones, status_today (active|inactive), min_days_active/days_active_to, max_countries, dedupe, date_from/date_to (delivery-window overlap), sort_by=date|days_active, sort_order, limit, cursor. Countries overrides country. Categories and ai_subcategory combine with OR on TikTok. title and text both search the ad text; advertiser searches the advertiser name. date_from/date_to selects ads whose delivery window overlaps the requested inclusive period, not ads launched in that period. Active means shown within the last 3 calendar days. Unsupported filters (including hub_domains, folder_id, saved and landing_domain_exact) are REJECTED before charging; no filter is silently ignored. Default TikTok limit is 10; each delivered row costs 100 tokens.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | max ad rows to return (maximum 200). Meta defaults to 20 at 1 token per delivered row; TikTok defaults to 10 at 100 tokens per delivered row. | |
| query | No | free-text keyword to match in ad title, body or link | |
| saved | No | saved scope selector. Use saved=all to restrict results to ads saved in ANY favorites folder, matching the saved-ads view on /ads. | |
| cursor | No | pagination cursor from a previous response's next_cursor | |
| dedupe | No | when true, collapse duplicates so each unique creative appears once | |
| source | No | ad corpus to search: meta (default — the Facebook/Meta library, 1 token per delivered row) or tiktok (the TikTok corpus — ⚠️ PAID: 100 tokens per DELIVERED TikTok ad row, embedded media links included; Pro plan required; default limit drops to 10). platforms=[tiktok] does NOT do this. | |
| channel | No | messaging channel the ad sends traffic to: whatsapp or telegram. Filters to ads landing on that channel's chat domains (whatsapp: api.whatsapp.com/wa.me/chat.whatsapp.com; telegram: t.me). Use for 'ads/bundles going to WhatsApp/Telegram'. | |
| country | No | single ISO country code filter (legacy form, e.g. US, BR, DE). Prefer countries[] for UI parity and multi-country requests. | |
| date_to | No | Meta: only ads whose Facebook launch date is on or before this date (YYYY-MM-DD); combine with date_from in a closed window of at most 31 calendar days for an exact in-call total. TikTok: delivery-window overlap; the first delivery date must be on or before date_to | |
| formats | No | creative formats: video, carousel, single, dynamic, carousel_multilang, carousel_multilang_categorized. Multilang upload detection is independent of languages; the categorized variant additionally requires an AI category. Values combine with OR. Search costs 1 token per row; opening a multilang ad costs 50 tokens. | |
| page_id | No | point lookup: only ads from this Facebook page id; also accepts a pasted facebook.com page link in any form (vanity, profile id, profile.php, Ads Library view_all_page_id) — resolved to the canonical page id automatically | |
| sort_by | No | TikTok: date or days_active only. Meta: order results by: date (default, newest first), most_popular, folder_added or most_reused_creative. most_reused_creative ranks ads by how many ads share the same exact creative within the selected filters and dates; default/desc gives most reused first, asc gives least reused first. It requires a specific advertiser/page/pixel/webmaster/query/landing-domain/creative anchor or a launch/discovery window of at most 31 days. Rows include semantic same-creative reuse counts and creative_reuse_status. folder_added is only meaningful on folder-scoped or saved=all views; most_popular is the Trending preset ranking | |
| pixel_id | No | point lookup: only ads carrying this Facebook pixel id | |
| countries | No | ISO country codes to filter by. Mirrors the /ads UI multi-country picker. With country_match=any (default) an ad matches when at least one selected geo is present; with country_match=only every known geo must belong to this set. | |
| date_from | No | Meta: only ads whose Facebook launch/delivery start date is on or after this date (YYYY-MM-DD). THE canonical date axis for 'new ads in a window' questions — new means LAUNCHED ON FACEBOOK in that window, not indexed by spytrend. Combine with date_to in a closed window of at most 31 calendar days for an exact in-call total. TikTok: delivery-window overlap; the last delivery date must be on or after date_from, not necessarily the launch date | |
| folder_id | No | restrict results to ads saved in this favorites folder UUID. Mirrors the /ads favorites page scope. | |
| languages | No | ISO language codes the ad targets (e.g. en, pt, es). The /ads Language filter. | |
| platforms | No | platform names to include (e.g. facebook, instagram) | |
| search_in | No | which field the free-text query matches: all (default), title, advertiser or text. The /ads search-scope toggle. | |
| categories | No | AI category slugs to include. Use the underscore slug form, e.g. gambling_and_betting, ecommerce_and_retail, finance_and_banking, dating_and_relationships, healthcare_and_medical, impersonation_funnels, crypto_and_trading (NOT short words like 'gambling') | |
| media_type | No | filter by creative media type: image or video | |
| sort_order | No | sort direction: asc or desc (default desc) | |
| cta_buttons | No | call-to-action button labels (e.g. Shop Now, Learn More, Sign Up, Download). The /ads CTA filter. | |
| hub_domains | No | exact hub/destination domains to filter landings by (e.g. linktr.ee, wa.me). The visible /ads Hub filter sends these; hub_category stays the coarse category form. When channel is also set, the explicit domains and channel domains are combined deterministically into one OR-list. | |
| resolved_ip | No | point lookup: only ads whose landing domain resolves to this IP | |
| domain_zones | No | landing-domain TLD/zone without the dot (e.g. com, shop, online, xyz). The /ads domain-zone filter. | |
| hub_category | No | filter to ads whose landing destination is in this TOP-LEVEL hub category. Valid slugs: social, app_stores, shortlinks, amazon, ecommerce, popular, platforms (NOT 'social_media' — the slug is 'social'). For a specific messaging channel use the channel param instead - hub_category does NOT accept whatsapp/telegram sub-slugs. | |
| status_today | No | current status: active, inactive or vanished | |
| webmaster_id | No | return only ads from this webmaster id (from search_webmasters) | |
| advertiser_id | No | return only ads from this advertiser id (from search_advertisers) | |
| country_match | No | how the countries filter matches: any (default, overlap semantics) or only (strict subset semantics). The /ads UI sends any explicitly when countries are selected. | |
| first_seen_to | No | only ads spytrend first INDEXED on or before this date (YYYY-MM-DD) — internal discovery date, not the Facebook launch date; for 'new ads' questions use date_from/date_to. See first_seen_from for exact-total windows | |
| max_countries | No | with countries or country selected, allow at most this many additional GEOs outside that selected set; without a selected GEO, allow at most this many total GEOs | |
| ai_subcategory | No | AI subcategory slugs to include under the chosen ai_category set. Mirrors the visible /ads subcategory drill-down. | |
| days_active_to | No | upper bound of the days-active range — only ads running at most this many days | |
| landing_domain | No | landing domain filter. By default it preserves the historical /ads behavior: match this hostname AND its subdomains. Set landing_domain_exact=true to restrict to the normalized hostname itself only. | |
| max_body_chars | No | only ads whose AD TEXT is at most this many characters long (UTF-8 characters, not bytes). Ads with no text are NOT returned as length 0 — the length filters cover only ads with known non-empty text. Combine with min_body_chars for a closed length range, e.g. 100..500 | |
| max_page_likes | No | only ads from pages with at most this many likes | |
| min_body_chars | No | only ads whose AD TEXT is at least this many characters long (UTF-8 characters, not bytes). Both length filters match only ads whose text length is KNOWN and non-zero: ads with no text at all are outside the length axis entirely and are returned by neither direction. Pairs with the body_chars field returned on every row; the ad text itself is served by get_ad, not by this list | |
| min_page_likes | No | only ads from pages with at least this many likes | |
| platforms_mode | No | how the platforms filter combines: any (OR, default) or all (AND — the ad must run on EVERY selected platform). The /ads platform match-mode toggle. | |
| first_seen_from | No | only ads spytrend first INDEXED on or after this date (YYYY-MM-DD) — an internal discovery date, NOT the ad's Facebook launch date. Do NOT use it for 'new ads recently' questions: spytrend also indexes OLD ads late, so late-indexed old ads would pollute the answer — use date_from/date_to (Facebook launch) for market newness. Exact totals: any closed window up to 31 days; a single-country request with only AI-category filters can use the canonical daily cube for up to 90 days or from this date through today | |
| min_days_active | No | only ads running at least this many days (e.g. 30 → long-running 'winning' ads). Lower bound of the days-active range | |
| contains_in_links | No | match a fragment inside the ad's tracking links - e.g. a pixel id, sub id or UTM fragment like 'pixel_id=123', 'sub1=', 'utm_campaign=xyz' | |
| landing_domain_exact | No | when true, restrict landing_domain to the normalized hostname itself and exclude subdomains. Mirrors the /ads UI exact-host toggle. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | ||
| meta | No | ||
| source | No | ||
| analysis | No | ||
| pagination | Yes | ||
| tiktok_data | No | ||
| auto_applied_verticals | No |