Skip to main content
Glama

Search ads

search_ads
Read-only

Search 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. EU transparency: ads delivered in the EU carry a disclosure with eu_total_reach (accounts reached inside the EU over the ad's lifetime — reach, not impressions or spend), the countries it was delivered in, audience gender/age and the payer. Filter on it with eu_only, eu_countries (+ eu_countries_match), eu_reach_min/max, eu_gender, eu_age_min/max and eu_payer; every result row then carries an eu_transparency block, and get_ad adds the country × age × gender breakdown. Meta source only. 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

TableJSON Schema
NameRequiredDescriptionDefault
limitNomax 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.
queryNofree-text keyword to match in ad title, body or link
savedNosaved scope selector. Use saved=all to restrict results to ads saved in ANY favorites folder, matching the saved-ads view on /ads.
cursorNopagination cursor from a previous response's next_cursor
dedupeNowhen true, collapse duplicates so each unique creative appears once
sourceNoad 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.
channelNomessaging 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'.
countryNosingle ISO country code filter (legacy form, e.g. US, BR, DE). Prefer countries[] for UI parity and multi-country requests.
date_toNoMeta: 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
eu_onlyNowhen true, return only ads that carry an EU transparency disclosure (reach inside the EU, audience, payer). Any other eu_* filter implies it.
formatsNocreative 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_idNopoint 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_byNoTikTok: 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
eu_payerNoexact payer name from the EU disclosure, case-insensitive (the legal entity that paid for the ad)
pixel_idNopoint lookup: only ads carrying this Facebook pixel id
countriesNoISO 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_fromNoMeta: 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
eu_genderNoaudience gender from the EU disclosure: women or men (ads aimed at all genders match neither)
folder_idNorestrict results to ads saved in this favorites folder UUID. Mirrors the /ads favorites page scope.
languagesNoISO language codes the ad targets (e.g. en, pt, es). The /ads Language filter.
platformsNoplatform names to include (e.g. facebook, instagram)
search_inNowhich field the free-text query matches: all (default), title, advertiser or text. The /ads search-scope toggle.
categoriesNoAI 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')
eu_age_maxNoupper bound of the audience age window, 13-65
eu_age_minNolower bound of the audience age window, 13-65; matches ads whose disclosed age range overlaps the window
media_typeNofilter by creative media type: image or video
sort_orderNosort direction: asc or desc (default desc)
cta_buttonsNocall-to-action button labels (e.g. Shop Now, Learn More, Sign Up, Download). The /ads CTA filter.
hub_domainsNoexact 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_ipNopoint lookup: only ads whose landing domain resolves to this IP
domain_zonesNolanding-domain TLD/zone without the dot (e.g. com, shop, online, xyz). The /ads domain-zone filter.
eu_countriesNoISO-2 EU/EEA countries where the ad was actually delivered according to its EU disclosure (e.g. DE, FR). This is delivered reach, not the targeting filter in countries.
eu_reach_maxNomaximum EU reach (accounts reached inside the EU)
eu_reach_minNominimum EU reach: accounts reached inside the EU over the ad's lifetime
hub_categoryNofilter 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_todayNocurrent status: active, inactive or vanished
webmaster_idNoreturn only ads from this webmaster id (from search_webmasters)
advertiser_idNoreturn only ads from this INTERNAL advertiser UUID — search_advertisers field advertiser_id, an ad row's advertiser_id, or get_trends dimension=advertisers. A Facebook page id or facebook.com page link sent here is applied as page_id (reported in params.normalized); any other value is rejected. For a page id prefer page_id (search_advertisers field external_id).
country_matchNohow 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_toNoonly 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_countriesNowith 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_subcategoryNoAI subcategory slugs to include under the chosen ai_category set. Mirrors the visible /ads subcategory drill-down.
days_active_toNoupper bound of the days-active range — only ads running at most this many days
landing_domainNolanding 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_charsNoonly 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_likesNoonly ads from pages with at most this many likes
min_body_charsNoonly 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_likesNoonly ads from pages with at least this many likes
platforms_modeNohow the platforms filter combines: any (OR, default) or all (AND — the ad must run on EVERY selected platform). The /ads platform match-mode toggle.
exclude_domainsNoHide ads whose landing domain is one of these or its subdomain, e.g. appsflyer.com, play.google.com
first_seen_fromNoonly 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_activeNoonly ads running at least this many days (e.g. 30 → long-running 'winning' ads). Lower bound of the days-active range
contains_in_linksNomatch 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'
eu_countries_matchNohow eu_countries matches: any (default, delivered in at least one) or all (delivered in every listed country)
landing_domain_exactNowhen true, restrict landing_domain to the normalized hostname itself and exclude subdomains. Mirrors the /ads UI exact-host toggle.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
dataNo
metaNo
errorNoPresent only when isError is true: machine-readable failure. The human explanation stays in content.
paramsNoRequest parameter echo: applied = parameters that shaped this result; normalized = parameters rewritten before applying (alias, type coercion, resolved id); ignored = parameters that were accepted but NOT applied, with the reason.
sourceNo
analysisNo
paginationNo
tiktok_dataNo
auto_applied_verticalsNo

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • addedOutput schema / properties / data / items / properties / is_cloaked
      Added value: +{
      +  "type": "boolean"
      +}
  2. Changed1 schema field changed
    • addedInput schema / properties / exclude_domains
      Added value: +{
      +  "description": "Hide ads whose landing domain is one of these or its subdomain, e.g. appsflyer.com, play.google.com",
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": [
      +    "null",
      +    "array"
      +  ]
      +}
  3. Changed4 schema fields changed
    • changedInput schema / properties / advertiser_id / description
      Previous value: -"return only ads from this advertiser id (from search_advertisers)"New value: +"return only ads from this INTERNAL advertiser UUID — search_advertisers field advertiser_id, an ad row's advertiser_id, or get_trends dimension=advertisers. A Facebook page id or facebook.com page link sent here is applied as page_id (reported in params.normalized); any other value is rejected. For a page id prefer page_id (search_advertisers field external_id)."
    • addedOutput schema / properties / error
      Added value: +{
      +  "description": "Present only when isError is true: machine-readable failure. The human explanation stays in content.",
      +  "properties": {
      +    "code": {
      +      "description": "fine-grained, stable failure code (e.g. invalid_arguments, backend_busy)",
      +      "type": "string"
      +    },
      +    "kind": {
      +      "description": "failure class: invalid_argument, not_found, permission_denied, unauthenticated, quota_exceeded, rate_limited, temporarily_unavailable, unavailable_until_ready, unsupported, internal",
      +      "type": "string"
      +    },
      +    "message": {
      +      "description": "the same human text as content[0]",
      +      "type": "string"
      +    },
      +    "outcome": {
      +      "description": "activity-feed outcome class",
      +      "type": "string"
      +    },
      +    "param": {
      +      "description": "the request parameter the failure is about, when known",
      +      "type": "string"
      +    },
      +    "retry_after_seconds": {
      +      "description": "wait this long before retrying",
      +      "type": "integer"
      +    },
      +    "retryable": {
      +      "description": "true when repeating the SAME call can succeed (after retry_after_seconds when present)",
      +      "type": "boolean"
      +    }
      +  },
      +  "type": "object"
      +}
    • addedOutput schema / properties / params
      Added value: +{
      +  "description": "Request parameter echo: applied = parameters that shaped this result; normalized = parameters rewritten before applying (alias, type coercion, resolved id); ignored = parameters that were accepted but NOT applied, with the reason.",
      +  "properties": {
      +    "applied": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    "ignored": {
      +      "items": {
      +        "properties": {
      +          "param": {
      +            "type": "string"
      +          },
      +          "reason": {
      +            "type": "string"
      +          },
      +          "to": {
      +            "description": "the parameter it was applied as, when renamed",
      +            "type": "string"
      +          },
      +          "value": {
      +            "description": "the value actually applied, when rewritten",
      +            "type": "string"
      +          }
      +        },
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "normalized": {
      +      "items": {
      +        "properties": {
      +          "param": {
      +            "type": "string"
      +          },
      +          "reason": {
      +            "type": "string"
      +          },
      +          "to": {
      +            "description": "the parameter it was applied as, when renamed",
      +            "type": "string"
      +          },
      +          "value": {
      +            "description": "the value actually applied, when rewritten",
      +            "type": "string"
      +          }
      +        },
      +        "type": "object"
      +      },
      +      "type": "array"
      +    }
      +  },
      +  "type": "object"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "data",
      -  "pagination"
      -]
  4. Changed1 schema field changed
    • addedOutput schema / properties / data / items / properties / webmasters
      Added value: +{
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      },
      +      "matched_by": {
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id"
      +    ],
      +    "type": "object"
      +  },
      +  "type": [
      +    "null",
      +    "array"
      +  ]
      +}
  5. Changed10 schema fields changed
    • addedInput schema / properties / eu_age_max
      Added value: +{
      +  "description": "upper bound of the audience age window, 13-65",
      +  "type": [
      +    "null",
      +    "integer"
      +  ]
      +}
    • addedInput schema / properties / eu_age_min
      Added value: +{
      +  "description": "lower bound of the audience age window, 13-65; matches ads whose disclosed age range overlaps the window",
      +  "type": [
      +    "null",
      +    "integer"
      +  ]
      +}
    • addedInput schema / properties / eu_countries
      Added value: +{
      +  "description": "ISO-2 EU/EEA countries where the ad was actually delivered according to its EU disclosure (e.g. DE, FR). This is delivered reach, not the targeting filter in countries.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": [
      +    "null",
      +    "array"
      +  ]
      +}
    • addedInput schema / properties / eu_countries_match
      Added value: +{
      +  "description": "how eu_countries matches: any (default, delivered in at least one) or all (delivered in every listed country)",
      +  "enum": [
      +    "any",
      +    "all"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / eu_gender
      Added value: +{
      +  "description": "audience gender from the EU disclosure: women or men (ads aimed at all genders match neither)",
      +  "enum": [
      +    "women",
      +    "men"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / eu_only
      Added value: +{
      +  "description": "when true, return only ads that carry an EU transparency disclosure (reach inside the EU, audience, payer). Any other eu_* filter implies it.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / eu_payer
      Added value: +{
      +  "description": "exact payer name from the EU disclosure, case-insensitive (the legal entity that paid for the ad)",
      +  "type": "string"
      +}
    • addedInput schema / properties / eu_reach_max
      Added value: +{
      +  "description": "maximum EU reach (accounts reached inside the EU)",
      +  "type": [
      +    "null",
      +    "integer"
      +  ]
      +}
    • addedInput schema / properties / eu_reach_min
      Added value: +{
      +  "description": "minimum EU reach: accounts reached inside the EU over the ad's lifetime",
      +  "type": [
      +    "null",
      +    "integer"
      +  ]
      +}
    • addedOutput schema / properties / data / items / properties / eu_transparency
      Added value: +{
      +  "additionalProperties": false,
      +  "properties": {
      +    "age_max": {
      +      "type": "integer"
      +    },
      +    "age_min": {
      +      "type": "integer"
      +    },
      +    "beneficiary": {
      +      "type": "string"
      +    },
      +    "breakdown": {
      +      "items": {
      +        "additionalProperties": false,
      +        "properties": {
      +          "age_range": {
      +            "type": "string"
      +          },
      +          "country": {
      +            "type": "string"
      +          },
      +          "female": {
      +            "maximum": 4294967295,
      +            "minimum": 0,
      +            "type": "integer"
      +          },
      +          "male": {
      +            "maximum": 4294967295,
      +            "minimum": 0,
      +            "type": "integer"
      +          },
      +          "unknown": {
      +            "maximum": 4294967295,
      +            "minimum": 0,
      +            "type": "integer"
      +          }
      +        },
      +        "required": [
      +          "country",
      +          "age_range",
      +          "female",
      +          "male",
      +          "unknown"
      +        ],
      +        "type": "object"
      +      },
      +      "type": [
      +        "null",
      +        "array"
      +      ]
      +    },
      +    "checked_at": {
      +      "type": "string"
      +    },
      +    "eu_total_reach": {
      +      "minimum": 0,
      +      "type": "integer"
      +    },
      +    "excluded_locations": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": [
      +        "null",
      +        "array"
      +      ]
      +    },
      +    "gender_audience": {
      +      "type": "string"
      +    },
      +    "has_data": {
      +      "type": "boolean"
      +    },
      +    "payer": {
      +      "type": "string"
      +    },
      +    "reach_countries": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": [
      +        "null",
      +        "array"
      +      ]
      +    },
      +    "targeted_locations": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": [
      +        "null",
      +        "array"
      +      ]
      +    }
      +  },
      +  "required": [
      +    "has_data",
      +    "eu_total_reach"
      +  ],
      +  "type": [
      +    "null",
      +    "object"
      +  ]
      +}
  6. Changed5 schema fields changed
    • changedInput schema / properties / date_from / description
      Previous value: -"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"New value: +"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"
    • changedInput schema / properties / date_to / description
      Previous value: -"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"New value: +"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"
    • changedInput schema / properties / limit / description
      Previous value: -"max rows to return (default 20; each delivered row costs 1 token). IGNORED on geo and geo_timeline — both return their complete set (geo: the whole per-country stock table, ~194 rows; geo_timeline: every populated date×country point) while charging only the page size you asked (a flat 20 by default). Those responses can be large: pass verbosity=compact to drop the heavy per-row nested structures."New value: +"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."
    • changedInput schema / properties / sort_by / description
      Previous value: -"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"New value: +"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"
    • changedInput schema / properties / sort_by / enum
      Previous value: -[
      -  "date",
      -  "most_popular",
      -  "folder_added",
      -  "most_reused_creative"
      -]New value: +[
      +  "date",
      +  "most_popular",
      +  "folder_added",
      +  "most_reused_creative",
      +  "days_active"
      +]
  7. Changed15 schema fields changed
    • addedOutput schema / properties / data / items / properties / creative_destination_domain
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / data / items / properties / creative_destination_url
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / data / items / properties / is_multilang
      Added value: +{
      +  "type": [
      +    "null",
      +    "boolean"
      +  ]
      +}
    • addedOutput schema / properties / data / items / properties / media / items / properties / body
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / data / items / properties / media / items / properties / carousel_position
      Added value: +{
      +  "type": [
      +    "null",
      +    "integer"
      +  ]
      +}
    • addedOutput schema / properties / data / items / properties / media / items / properties / claimed_domain
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / data / items / properties / media / items / properties / cloaking_detected
      Added value: +{
      +  "type": "boolean"
      +}
    • addedOutput schema / properties / data / items / properties / media / items / properties / fallback_urls
      Added value: +{
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": [
      +    "null",
      +    "array"
      +  ]
      +}
    • addedOutput schema / properties / data / items / properties / media / items / properties / is_alive
      Added value: +{
      +  "type": [
      +    "null",
      +    "boolean"
      +  ]
      +}
    • addedOutput schema / properties / data / items / properties / media / items / properties / link_url
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / data / items / properties / media / items / properties / title
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / data / items / properties / opening_price
      Added value: +{
      +  "additionalProperties": false,
      +  "properties": {
      +    "billing_basis": {
      +      "type": "string"
      +    },
      +    "status": {
      +      "type": "string"
      +    },
      +    "tokens": {
      +      "type": [
      +        "null",
      +        "integer"
      +      ]
      +    },
      +    "unit": {
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "status",
      +    "tokens",
      +    "unit",
      +    "billing_basis"
      +  ],
      +  "type": [
      +    "null",
      +    "object"
      +  ]
      +}
    • addedOutput schema / properties / data / items / properties / original_landing_domain
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / data / items / properties / primary_creative
      Added value: +{
      +  "additionalProperties": false,
      +  "properties": {
      +    "body": {
      +      "type": "string"
      +    },
      +    "carousel_position": {
      +      "type": [
      +        "null",
      +        "integer"
      +      ]
      +    },
      +    "claimed_domain": {
      +      "type": "string"
      +    },
      +    "cloaking_detected": {
      +      "type": "boolean"
      +    },
      +    "expiring": {
      +      "type": "boolean"
      +    },
      +    "fallback_urls": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": [
      +        "null",
      +        "array"
      +      ]
      +    },
      +    "is_alive": {
      +      "type": [
      +        "null",
      +        "boolean"
      +      ]
      +    },
      +    "link_url": {
      +      "type": "string"
      +    },
      +    "media_type": {
      +      "type": "string"
      +    },
      +    "snapshot_url": {
      +      "type": "string"
      +    },
      +    "thumbnail_url": {
      +      "type": "string"
      +    },
      +    "title": {
      +      "type": "string"
      +    },
      +    "url": {
      +      "type": "string"
      +    }
      +  },
      +  "type": [
      +    "null",
      +    "object"
      +  ]
      +}
    • addedOutput schema / properties / data / items / properties / primary_creative_selection
      Added value: +{
      +  "additionalProperties": false,
      +  "properties": {
      +    "carousel_position": {
      +      "type": [
      +        "null",
      +        "integer"
      +      ]
      +    },
      +    "reason": {
      +      "type": "string"
      +    },
      +    "status": {
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "status"
      +  ],
      +  "type": [
      +    "null",
      +    "object"
      +  ]
      +}
  8. Changed1 schema field changed
    • changedInput schema / properties / formats / description
      Previous value: -"creative format filter from the visible /ads Format control: video, carousel, single or dynamic. Distinct from platforms, which filters publisher placements like facebook/instagram."New value: +"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."
  9. Changed2 schema fields changed
    • changedInput schema / properties / max_body_chars / description
      Previous value: -"only ads whose AD TEXT is at most this many characters long (UTF-8 characters, not bytes). Combine with min_body_chars for a closed length range, e.g. 100..500"New value: +"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"
    • changedInput schema / properties / min_body_chars / description
      Previous value: -"only ads whose AD TEXT is at least this many characters long (UTF-8 characters, not bytes). Pairs with the body_chars field returned on every row; the ad text itself is served by get_ad, not by this list"New value: +"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"
  10. First observed

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only cover the read-only/open-world safety profile; the description adds far more: exact-count contracts (31-day windows, 90-day single-country cube), the 'unavailable is PENDING, not zero' rule, page/row token costs, TikTok premium pricing and Pro-plan gating, and EU disclosure semantics. These are behavioral traits no annotation or schema field conveys.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded well (purpose and what is excluded come first), and the massive length is partly justified by 55 parameters and two corpora. However it is highly redundant: token pricing is restated three or four times, and the TikTok pricing/quota warnings repeat, inflating the description well past what the same information needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 55 parameters, an output schema, two source corpora and a complex exact-count contract, the description covers everything an agent needs: pagination cursor/total semantics, count reliability states, pricing, plan gating, and the source-switch behavior. Nothing material for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds genuine cross-parameter meaning beyond the schema: source=tiktok vs platforms (Meta placements only), country vs countries, landing_domain vs landing_domain_exact, and the launch-date vs discovery-date distinction. It largely restates the filter list that the schema already documents, which keeps it short of a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (search the spytrend.com ad database) and precisely scopes what is returned (metadata only, not ad text, not media). It explicitly names the sibling tools that cover the excluded data (get_ad, get_media) and the sibling that handles aggregate rankings (get_trends), so an agent can distinguish it immediately.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Extensive when/when-not routing: use landing_domain NOT query for domain lookups; use date_from/date_to and never first_seen_from for 'new ads'; use get_advertiser/get_webmaster instead of paginating for exact advertiser counts; use get_trends for aggregates; pass ids to get_media to download and add_to_favorites to save. Alternatives and exclusions are explicit, not implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.