360 Secure SkillHub
360 安全技能中心

scavio-google-ads

该Skill对接谷歌广告透明度中心,提供三大核心功能:将品牌/域名解析为验证后的谷歌广告主ID;拉取广告主在搜索、YouTube等全平台的所有广告;获取单条创意的区域分布、曝光时段等完整历史。适用于竞品调研、政治广告合规核查等场景,输入品牌/域名等参数,输出结构化JSON,需配置SCAVIO_API_KEY,单请求消耗1 credits。

scavio-ai
scavio-ai
热度 21.61
最近更新 2026-08-19 00:40:56
分类:营销依赖:第三方 API应用场景:市场分析 / 研究 / 社交媒体 / 报告撰写

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add scavio-google-ads
Metadata
name
scavio-google-ads
description
Resolve a brand or domain to a Google advertiser id, then pull every ad it runs across Search, YouTube, Shopping, Maps and Play, and open one creative with its full region and impression history. 3 endpoints, 1 credit each.
version
1.0.0
tags
google-ads, ads-transparency, ad-library, competitor-ads, ad-creative, ad-intelligence, ppc-research, paid-search, youtube-ads, political-ads, advertiser-lookup, marketing-research, agents, langchain, crewai, autogen, structured-data, json, ai-agents
metadata
{"openclaw":{"requires":{"env":["SCAVIO_API_KEY"]},"primaryEnv":"SCAVIO_API_KEY","timeout":120,"throttle":1,"emoji":"📢","homepage":"https://scavio.dev/docs/google-ads-advertisers"}}

Google Ads Transparency via Scavio

Resolve a brand name or domain to a verified Google advertiser id, pull every ad that advertiser is running across Search, YouTube, Shopping, Maps and Play, and open any single creative for its full per-region history. All three endpoints return structured JSON from Google's Ads Transparency Center.

When to trigger

Use this skill when the user asks to:

  • See what ads a competitor is running on Google, YouTube or Shopping right now
  • Find a brand's verified Google advertiser id
  • Pull ad creatives with their first-seen and last-seen dates and how long they actually ran
  • Break one creative down by region, surface and impression bucket
  • Research political or issue ads and their funder disclosures
  • Build ad-intelligence, creative-swipe or competitor-monitoring pipelines

Setup

Get a free API key at scavio.dev (50 free credits to get started, no card required):

export SCAVIO_API_KEY=sk_live_your_key

Every request is a POST with a JSON body and:

Authorization: Bearer $SCAVIO_API_KEY

Endpoints

Base URL: https://api.scavio.dev. All paths are under /api/v1/googleads. Every endpoint costs 1 credit.

Endpoint Credits What it returns
POST /api/v1/googleads/advertisers 1 Start here. Brand or domain to advertiser id. No pagination.
POST /api/v1/googleads/search 1 Every ad for one advertiser, 100 per cursor page
POST /api/v1/googleads/creative 1 One creative in full, with its region and impression history

Workflow

Look up first. /search and /creative are keyed by advertiser_id.

  1. Resolve: call /googleads/advertisers with a brand name or a domain. A name query returns two kinds of row - advertiser rows with the id, verified name, verification country and total ad count, and domain rows carrying a website. A domain-shaped query returns domains only. A full URL is fine - Google reduces it to the registrable host itself. Pass region for any non-US advertiser: this endpoint defaults to the United States, so a brand that runs no US ads returns an empty list, which reads like "not found" rather than "wrong country".
  2. Pull the ads: call /googleads/search with advertiser_id or domain, plus filters.
  3. Open one creative: call /googleads/creative with the advertiser_id and creative_id pair.

Query by domain when you need the domain back

domain is dropped entirely from every row when the query is by advertiser_id. Querying by domain is the only way to get that field back on each row. Choose the query shape based on what you need in the output.

Pagination

  • /search paginates by cursor: send cursor and read next_cursor back, 100 rows per page. Re-send the same filters alongside the cursor. next_cursor is null once the set is exhausted.
  • limit is capped at 100, and that is a hard upstream ceiling, not our policy. Google answers a request for more than 100 with zero rows rather than an error, so an over-large limit looks like "this advertiser has no ads".
  • /advertisers does not paginate - it is an autocomplete, roughly 20 rows per arm.
  • /creative does not paginate.

Parameters

Advertisers (/advertisers)

Parameter Type Default Description
query string required Brand name or domain (1-200 chars)
region string US ISO alpha-2 (US, GB, DE) or a Google geo criteria id as a string (2-12 chars). Defaults to the United States, not worldwide - the lookup runs against one country index at a time, so an advertiser who runs no US ads comes back as an empty list rather than an error. Set it to a country the advertiser actually advertises in.
limit integer 10 1-20, per arm - advertisers and domains are capped separately, so a name query can return up to twice this many rows

Search (/search)

Parameter Type Default Description
domain string one of Bare host, www host or full URL (1-253 chars), reduced to the registrable host. The only way to get domain back on each row.
advertiser_id string one of e.g. AR16735076323512287233 (3-40 chars). The shape is checked before any request, so a typo costs nothing.
region string -- ISO alpha-2 or a Google geo criteria id. Default: no region filter (worldwide).
format string -- text, image, video. Default: all formats.
platform string -- play, maps, search, shopping, youtube. Default: all surfaces.
topic string all all or political
limit integer 40 1-100. 100 is a hard upstream ceiling - larger returns zero rows.
cursor string -- next_cursor from the previous response. Re-send the same filters with it.

domain or advertiser_id is required.

The three format sets are disjoint. An advertiser's text, image and video ads share no creatives, so a format filter partitions the library rather than ranking within it. To see everything, omit format.

region scopes the deep links on every row, and the same advertiser can share zero creatives between two countries. A region-filtered empty result does not mean the advertiser is inactive.

Creative (/creative)

Parameter Type Default Description
advertiser_id string required 3-40 chars
creative_id string required 3-40 chars. Must belong to the advertiser_id sent with it - the lookup is keyed by the pair, and a mismatched pair is a 404.

Impressions and reach are EEA-only

impressions_min, impressions_max and first_shown come back null outside the EEA. Google is compelled to publish reach data by EU law and publishes it only where law requires, so a US creative simply has no impression figures. This is not a bug, not a parsing gap, and not something a retry will fix.

If the user wants reach numbers, query an EEA region. If they are looking at a US advertiser, tell them the data does not exist rather than reporting zero.

Everything Google publishes as a range

Google never publishes an exact ad count. The advertiser's headline total arrives as total_ads_min / total_ads_max, and impression buckets on /creative behave the same way - a row can carry a lower bound, an upper bound, or one of the two alone. Report ranges as ranges; never present a bound as a precise figure.

Examples

import requests

BASE = "https://api.scavio.dev"
# Your key from https://scavio.dev. Load it from your environment or secret
# store in real code - keep it out of source control.
API_KEY = "sk_your_key_here"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# 1. Resolve the brand to a verified advertiser id - always start here
who = requests.post(f"{BASE}/api/v1/googleads/advertisers", headers=HEADERS,
    json={"query": "Notion", "region": "DE", "limit": 20}).json()

# 2. Every ad they run in Germany. EEA region, so impressions are populated.
ads = requests.post(f"{BASE}/api/v1/googleads/search", headers=HEADERS,
    json={"advertiser_id": "AR16735076323512287233", "region": "DE",
          "platform": "youtube", "limit": 100}).json()

# 2b. Query by domain instead when you need `domain` back on each row
by_domain = requests.post(f"{BASE}/api/v1/googleads/search", headers=HEADERS,
    json={"domain": "notion.so", "region": "DE", "limit": 100}).json()

# 3. One creative in full - the advertiser_id + creative_id PAIR
creative = requests.post(f"{BASE}/api/v1/googleads/creative", headers=HEADERS,
    json={"advertiser_id": "AR16735076323512287233", "creative_id": "CR1234567890"}).json()

Cursor paging. Re-send the same filters every time, and cap the walk:

def all_ads(body, max_pages=5):
    """1 credit per page, 100 rows per page. Same filters on every call."""
    cursor, pages = None, []
    for _ in range(max_pages):
        payload = {**body, **({"cursor": cursor} if cursor else {})}
        data = requests.post(f"{BASE}/api/v1/googleads/search",
                             headers=HEADERS, json=payload).json()["data"]
        pages.append(data)
        cursor = data.get("next_cursor")
        if not cursor:
            break
    return pages

pages = all_ads({"advertiser_id": "AR16735076323512287233", "region": "DE", "limit": 100})

Response shapes

Every response uses the envelope { data, response_time, credits_used, credits_remaining }.

  • advertisers - two row kinds in one list. advertiser rows carry the id, verified name, verification country and total ad count as a range; domain rows carry a website.
  • search - the creative (archived image, rich-media bundle, Google's renderer link, dimensions), advertiser id and name, format, first and last seen dates, how many days it actually ran, plus total_ads_min / total_ads_max and next_cursor. domain appears on each row only when the query was by domain.
  • creative - every size variation of the asset, the impression bucket, the per-region breakdown with first and last shown dates and a per-surface impression split inside each region, the format, Google's category label, and the funder disclosure on political ads. This is the only endpoint carrying a creative's history.

Guardrails

  • Every call is 1 credit, including a 100-row page. Budget the walk before starting it.
  • Never send limit above 100. Google returns zero rows for an over-large request, which reads as "no ads" and is the single easiest way to report a false negative here.
  • Never report null impressions as zero impressions. Outside the EEA the figure is not published at all - say so.
  • Never convert total_ads_min / total_ads_max into a single number. Google publishes a range on purpose.
  • A format filter partitions a disjoint set. Do not describe a video-only pull as "all their ads".
  • A region-filtered empty result means nothing about the advertiser globally - the same advertiser can share zero creatives between two countries.
  • /creative is keyed by the advertiser + creative pair. Do not carry a creative id across advertisers.
  • Never fabricate advertiser ids, creative ids, run dates, impression figures or funder names. Only return API data.
  • This is public transparency data. When summarising political ads, quote the disclosure rather than characterising the funder.

Failure handling

  • 400 means an invalid or missing parameter - e.g. neither domain nor advertiser_id. The advertiser id shape is validated before any upstream request, so a typo there costs nothing. Fix and retry.
  • 401 means the API key is invalid or missing. Check SCAVIO_API_KEY.
  • 404 on /creative usually means the advertiser_id and creative_id do not belong together. Re-pull the creative id from /search for that advertiser.
  • 429 means rate or usage limit exceeded. Wait before retrying. See https://scavio.dev/docs/rate-limits.
  • 502 / 503 mean upstream is temporarily unavailable - wait a few seconds and retry.
  • Zero rows from /search with a large limit is almost always the 100 ceiling, not an empty advertiser. Drop limit to 100 and retry before concluding anything.
  • If SCAVIO_API_KEY is not set, prompt the user to export it before continuing.

Python SDK

langchain-scavio has no Google Ads Transparency tool - use the Scavio SDK directly:

pip install scavio==0.15.0
from scavio import ScavioClient

client = ScavioClient()  # reads SCAVIO_API_KEY

who = client.google_ads.advertisers("Notion", region="DE", limit=20)
page1 = client.google_ads.search(advertiser_id="AR16735076323512287233",
                                 region="DE", limit=100)
page2 = client.google_ads.search(advertiser_id="AR16735076323512287233",
                                 region="DE", limit=100,
                                 cursor=page1["data"]["next_cursor"])
creative = client.google_ads.creative("AR16735076323512287233", "CR1234567890")

JavaScript / TypeScript:

npm install scavio@0.15.0
import { Scavio } from "scavio";

const scavio = new Scavio(); // reads SCAVIO_API_KEY
const ads = await scavio.googleAds.search({ domain: "notion.so", region: "DE", limit: 100 });