Docs · Google ad details
Google ad details
One ad from Google's Ads Transparency Center: every version of the creative, where it ran and, for political ads, spend and targeting.
https://api.lurkapi.com /v1/google/adWhen to use this
Use it after company ads (google_company_ads) to see one ad's versions, the countries it ran in and its landing page. Pass the ad's Transparency Center link as url, or both advertiser_id and creative_id.
Each version has an imageUrl when Google saved it as a picture, and a previewUrl otherwise. For text and video ads we also read one live preview to get the headline, description, landing page and YouTube video. Text that only exists inside an image stays null, since we don't read text from images.
Google publishes overall impressions, spend and targeting for political ads only. Per-country impressions are also published for ads shown in the EU.
Returns 404 not_found if the ad doesn't exist.
Example request
Copy one and replace YOUR_API_KEY with your API key (get one free). The code versions read the key from the LURKAPI_API_KEY environment variable instead.
curl "https://api.lurkapi.com/v1/google/ad?url=https%3A%2F%2Fadstransparency.google.com%2Fadvertiser%2FAR16735076323512287233%2Fcreative%2FCR11071274990039990273%3Fregion%3DUS" \
-H "x-api-key: YOUR_API_KEY"Paste it into a terminal, with your key in place of YOUR_API_KEY.
const params = new URLSearchParams({
url: "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US",
});
const res = await fetch(`https://api.lurkapi.com/v1/google/ad?${params}`, {
headers: { "x-api-key": process.env.LURKAPI_API_KEY },
});
const data = await res.json();
if (!data.success) throw new Error(`${data.code}: ${data.error}`);
console.log(data.creativeRegions);Node 18+. Save it as lurk.mjs and run node lurk.mjs. It reads your key from LURKAPI_API_KEY.
import os
import requests
res = requests.get(
"https://api.lurkapi.com/v1/google/ad",
params={
"url": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US",
},
headers={"x-api-key": os.environ["LURKAPI_API_KEY"]},
timeout=60,
)
data = res.json()
if not data["success"]:
raise RuntimeError(f"{data['code']}: {data['error']}")
print(data["creativeRegions"])Run pip install requests, save it as lurk.py and run python lurk.py. It reads your key from LURKAPI_API_KEY.
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"time"
)
func main() {
params := url.Values{}
params.Set("url", "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US")
req, _ := http.NewRequest("GET", "https://api.lurkapi.com/v1/google/ad?"+params.Encode(), nil)
req.Header.Set("x-api-key", os.Getenv("LURKAPI_API_KEY"))
client := &http.Client{Timeout: 60 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
if data["success"] != true {
panic(fmt.Sprintf("%v: %v", data["code"], data["error"]))
}
out, _ := json.MarshalIndent(data["creativeRegions"], "", " ")
fmt.Println(string(out))
}Save it as main.go and run go run main.go. It reads your key from LURKAPI_API_KEY.
<?php
$params = http_build_query([
'url' => 'https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US',
]);
$context = stream_context_create(['http' => [
'header' => 'x-api-key: ' . getenv('LURKAPI_API_KEY'),
'timeout' => 60,
'ignore_errors' => true,
]]);
$body = file_get_contents('https://api.lurkapi.com/v1/google/ad?' . $params, false, $context);
$data = json_decode($body, true);
if (!$data['success']) {
throw new Exception("{$data['code']}: {$data['error']}");
}
print_r($data['creativeRegions']);Save it as lurk.php and run php lurk.php. It reads your key from LURKAPI_API_KEY.
require 'json'
require 'net/http'
uri = URI('https://api.lurkapi.com/v1/google/ad')
uri.query = URI.encode_www_form(
'url' => 'https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US',
)
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV.fetch('LURKAPI_API_KEY')
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', read_timeout: 60) do |http|
http.request(req)
end
data = JSON.parse(res.body)
raise "#{data['code']}: #{data['error']}" unless data['success']
puts JSON.pretty_generate(data['creativeRegions'])Save it as lurk.rb and run ruby lurk.rb. It reads your key from LURKAPI_API_KEY.
Parameters
Add them to the URL after a ?. Join several with &.
| Parameter | Description |
|---|---|
urlstringoptional | The ad's Transparency Center link, e.g. https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273. Required unless you pass both ids.
|
advertiser_idstringoptional | The advertiser's id (AR…), with creative_id, instead of url. |
creative_idstringoptional | The ad's id (CR…), with advertiser_id, instead of url. |
Example response
A real response, captured from the live API. Lists are trimmed to a couple of items, and long text such as media links is cut short with ….
Show the example response (2 KB)Hide
{
"success": true,
"credits_remaining": 999958,
"credits_charged": 1,
"advertiserId": "AR16735076323512287233",
"creativeId": "CR11071274990039990273",
"advertiserName": "Nike, Inc.",
"format": "video",
"adUrl": "https://adstransparency.google.com/advertiser/AR16735076323512287233/cre…",
"firstShown": null,
"lastShown": "2026-10-02T09:57:07.000Z",
"daysShown": null,
"overallImpressions": null,
"spend": null,
"targeting": null,
"targetingCategories": null,
"creativeRegions": [
{
"regionCode": "US",
"regionName": "United States"
}
],
"regionStats": [
{
"regionCode": "US",
"regionName": "United States",
"firstShown": null,
"lastShown": "2026-10-02",
"impressions": null,
"platformImpressions": []
}
],
"eeaStats": null,
"usStats": null,
"variations": [
{
"destinationUrl": "https://www.nike.com/retail/",
"headline": "Engineered for Max Airflow",
"description": "Maximum breathability meets ultra-light comfort in new Nike Aero-FIT styles.",
"allText": null,
"imageUrl": null,
"previewUrl": "https://displayads-formats.googleusercontent.com/ads/preview/content.js?…",
"videoId": "RZ1MLoOdWcc"
},
{
"destinationUrl": null,
"headline": null,
"description": null,
"allText": null,
"imageUrl": null,
"previewUrl": "https://displayads-formats.googleusercontent.com/ads/preview/content.js?…",
"videoId": null
}
]
}Response fields
Every field in a successful response, and what it means. A name with [] belongs to each item of a list: targeting.location.included[].criterionId is the criterionId of every item in targeting.location.included. null means there's no value.
Show all 95 fieldsHide
| Field | Type | Description |
|---|---|---|
| success | true | Always true here; errors have success: false. |
| credits_remaining | number | Your balance after this call. On anonymous playground calls: free tries left today. |
| credits_charged | number | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). |
| advertiserId | string | The advertiser's id (AR…). Pass it as advertiser_id to company ads. |
| creativeId | string | The ad's id (CR…). |
| advertiserName | stringcan be null | Who paid for the ad (Google's "paid for by" name), e.g. Nike, Inc.; it can differ from the advertiser account, e.g. for agencies. null when Google shows none, which is common (political ads, many EU ads). |
| format | stringcan be null | text, image or video. Google often saves text ads as a picture of the ad. |
| adUrl | string | The ad's page on the Ads Transparency Center. |
| firstShown | stringcan be null | When the ad was first shown, ISO 8601 (UTC). Google often leaves it out here; company ads always has it. |
| lastShown | stringcan be null | When the ad was last shown, ISO 8601 (UTC). Today's date for ads still running. |
| daysShown | numbercan be null | How many days the ad has been shown. A long run usually means the ad works. null when Google leaves it out. |
| overallImpressions | objectcan be null | Impressions as the range Google reports. Political ads only; null otherwise. |
| overallImpressions.min | number | Lower bound of impressions. |
| overallImpressions.max | number | Upper bound of impressions. |
| spend | objectcan be null | Spend as the range Google reports. Political ads only; null otherwise. |
| spend.currency | string | Currency code, e.g. USD. |
| spend.lower | number | Lower bound of the spend, in currency. |
| spend.upper | number | Upper bound of the spend, in currency. |
| targeting | objectcan be null | Who the ad targeted. Political ads only; null otherwise. Empty lists can still mean targeting was used: Google doesn't always list the groups. |
| targeting.age | object | Age groups (0-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65+, unknown) targeting. |
| targeting.age.included | string[] | Age groups (0-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65+, unknown) the ad targeted. |
| targeting.age.excluded | string[] | Age groups (0-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65+, unknown) the ad excluded. |
| targeting.gender | object | Genders (male, female, unknown) targeting. |
| targeting.gender.included | string[] | Genders (male, female, unknown) the ad targeted. |
| targeting.gender.excluded | string[] | Genders (male, female, unknown) the ad excluded. |
| targeting.location | object | Location targeting. |
| targeting.location.included | object[] | Locations the ad targeted. |
| targeting.location.included[].criterionId | string | Google's location id, e.g. 2840 (United States) or 1015229 (a city). |
| targeting.location.included[].name | stringcan be null | The country's name for country-level ids; null for cities and regions, which we don't resolve. |
| targeting.location.included[].fullName | stringcan be null | Same as name for countries; null for cities and regions. |
| targeting.location.included[].countryCodes | string[] | The country code for country-level ids, e.g. ["US"]; empty otherwise. |
| targeting.location.excluded | object[] | Locations the ad excluded. |
| targeting.location.excluded[].criterionId | string | Google's location id, e.g. 2840 (United States) or 1015229 (a city). |
| targeting.location.excluded[].name | stringcan be null | The country's name for country-level ids; null for cities and regions, which we don't resolve. |
| targeting.location.excluded[].fullName | stringcan be null | Same as name for countries; null for cities and regions. |
| targeting.location.excluded[].countryCodes | string[] | The country code for country-level ids, e.g. ["US"]; empty otherwise. |
| targetingCategories | objectcan be null | Which kinds of targeting the ad used, as Google lists them for ads shown in the EU and some political ads; null when Google doesn't say. |
| targetingCategories.demographics | object | Targeting by demographic info (age, gender, language, parental status, household income). |
| targetingCategories.demographics.included | boolean | Whether the ad targeted people by demographic info (age, gender, language, parental status, household income). |
| targetingCategories.demographics.excluded | boolean | Whether the ad excluded people by demographic info (age, gender, language, parental status, household income). |
| targetingCategories.locations | object | Targeting by geographic location. |
| targetingCategories.locations.included | boolean | Whether the ad targeted people by geographic location. |
| targetingCategories.locations.excluded | boolean | Whether the ad excluded people by geographic location. |
| targetingCategories.contextualSignals | object | Targeting by contextual signals (the pages they visit, the keywords they search). |
| targetingCategories.contextualSignals.included | boolean | Whether the ad targeted people by contextual signals (the pages they visit, the keywords they search). |
| targetingCategories.contextualSignals.excluded | boolean | Whether the ad excluded people by contextual signals (the pages they visit, the keywords they search). |
| targetingCategories.topicsOfInterest | object | Targeting by topics of interest. |
| targetingCategories.topicsOfInterest.included | boolean | Whether the ad targeted people by topics of interest. |
| targetingCategories.topicsOfInterest.excluded | boolean | Whether the ad excluded people by topics of interest. |
| targetingCategories.customerLists | object | Targeting by the advertiser's customer lists (data such as names and emails). |
| targetingCategories.customerLists.included | boolean | Whether the ad targeted people by the advertiser's customer lists (data such as names and emails). |
| targetingCategories.customerLists.excluded | boolean | Whether the ad excluded people by the advertiser's customer lists (data such as names and emails). |
| creativeRegions | object[] | Countries where the ad was shown. |
| creativeRegions[].regionCode | string | 2-letter country code, e.g. US. |
| creativeRegions[].regionName | string | Country name in English. |
| regionStats | object[] | Per-country delivery: dates and, for political ads and ads shown in the EU, impressions. |
| regionStats[].regionCode | string | 2-letter country code, e.g. US. |
| regionStats[].regionName | string | Country name in English. |
| regionStats[].firstShown | stringcan be null | First day shown in this country, YYYY-MM-DD; often null unless the ad is political or shown in the EU. |
| regionStats[].lastShown | stringcan be null | Last day shown in this country, YYYY-MM-DD. |
| regionStats[].impressions | objectcan be null | Impressions in this country, for political ads and ads shown in the EU; null otherwise. |
| regionStats[].impressions.min | number | Lower bound of impressions. |
| regionStats[].impressions.max | number | Upper bound of impressions. |
| regionStats[].platformImpressions | object[] | Impressions per Google product in this country, as Google reports them for ads shown in the EU; empty otherwise. |
| regionStats[].platformImpressions[].platform | stringcan be null | The Google product. |
| regionStats[].platformImpressions[].min | numbercan be null | Lower bound of impressions there. |
| regionStats[].platformImpressions[].max | numbercan be null | Upper bound of impressions there. |
| eeaStats | objectcan be null | Totals across the European Economic Area (the EU plus Iceland, Liechtenstein and Norway), which Google publishes for ads shown there; null otherwise. |
| eeaStats.firstShown | stringcan be null | First day shown across the EEA, YYYY-MM-DD; often null unless the ad is political or shown in the EU. |
| eeaStats.lastShown | stringcan be null | Last day shown across the EEA, YYYY-MM-DD. |
| eeaStats.impressions | objectcan be null | Impressions across the EEA, for political ads and ads shown in the EU; null otherwise. |
| eeaStats.impressions.min | number | Lower bound of impressions. |
| eeaStats.impressions.max | number | Upper bound of impressions. |
| eeaStats.platformImpressions | object[] | Impressions per Google product across the EEA, as Google reports them for ads shown in the EU; empty otherwise. |
| eeaStats.platformImpressions[].platform | stringcan be null | The Google product. |
| eeaStats.platformImpressions[].min | numbercan be null | Lower bound of impressions there. |
| eeaStats.platformImpressions[].max | numbercan be null | Upper bound of impressions there. |
| usStats | objectcan be null | Totals across the United States, which Google publishes for political ads; null otherwise. |
| usStats.firstShown | stringcan be null | First day shown across the US, YYYY-MM-DD; often null unless the ad is political or shown in the EU. |
| usStats.lastShown | stringcan be null | Last day shown across the US, YYYY-MM-DD. |
| usStats.impressions | objectcan be null | Impressions across the US, for political ads and ads shown in the EU; null otherwise. |
| usStats.impressions.min | number | Lower bound of impressions. |
| usStats.impressions.max | number | Upper bound of impressions. |
| usStats.platformImpressions | object[] | Impressions per Google product across the US, as Google reports them for ads shown in the EU; empty otherwise. |
| usStats.platformImpressions[].platform | stringcan be null | The Google product. |
| usStats.platformImpressions[].min | numbercan be null | Lower bound of impressions there. |
| usStats.platformImpressions[].max | numbercan be null | Upper bound of impressions there. |
| variations | object[] | Every version of the ad Google shows. For text and video ads, we read the live preview of the first version that has one. |
| variations[].destinationUrl | stringcan be null | Landing page, from the version's live preview. For search ads it can be just the display URL (e.g. nike.com); null when we didn't read one. |
| variations[].headline | stringcan be null | Headline, from the live preview; null for image-only versions and ones we didn't read. |
| variations[].description | stringcan be null | Description text, from the live preview; null for image-only versions and ones we didn't read. |
| variations[].allText | stringcan be null | All text on the ad. Always null for now, since we don't read text from images. |
| variations[].imageUrl | stringcan be null | Picture of this version; null when it only has a live preview. |
| variations[].previewUrl | stringcan be null | Google's live preview of this version (a script); null when there's an imageUrl. |
| variations[].videoId | stringcan be null | The YouTube video id of a video ad, from the live preview (youtube.com/watch?v=<id>); null otherwise. |
Cost and freshness
Each call costs 1 credit. Calls turned away before they reach the platform (invalid parameters, a key or balance problem, a rate limit) are free, and so are 5xx errors. A not_found is charged, since the platform was checked. How charging works.
Answers are cached for up to 12 hours. A repeat call within that time can return the same data, and costs the same.
Errors
A failed call returns success: false, an error message, a code your program can check, and a docs link. These are the errors this endpoint can return. All error codes.
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_api_key | No API key was sent. Put it in the x-api-key header. |
| 401 | invalid_api_key | This key doesn't exist or was revoked. |
| 402 | insufficient_credits | Not enough credits for this call. Buy a pack, or wait for tomorrow's free top-up. |
| 402 | over_max_credits | This call would cost more than the max_credits you set. The error says what it needs; raise max_credits to allow it. Not charged. |
| 403 | account_suspended | This account is suspended. Email support@lurkapi.com. Not charged. |
| 404 | not_found | What you asked for (a post, an ad, a subreddit) doesn't exist, or there's no endpoint at that path. Charged if the platform was checked. |
| 405 | method_not_allowed | Endpoints only take GET, with parameters in the URL. |
| 429 | rate_limited | Too many calls in a short time. Wait as many seconds as the retry-after header says, then retry. |
| 500 | internal_error | Something broke on our side. Retry. Not charged. |
| 502 | upstream_error | The platform didn't send a usable answer. Retry. Not charged. |
| 503 | upstream_busy | All our connections to the platform are busy. Retry in a few seconds. Not charged. |
| 504 | upstream_timeout | The platform took over 30 seconds to answer (90 for web pages). Retry. Not charged. |
Use it in Claude
Connect LurkAPI to Claude and ask in plain English. This endpoint is the google_ad tool. For example:
Use LurkAPI's google_ad tool with url "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US", then summarize what you find.Claude calls google_ad with arguments like these, and each call costs 1 credit:
{"url": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US"}To save tokens, tool results leave out empty fields, keep one URL per image or video, and give transcripts as plain text unless you pass timestamps=true. A result over 45,000 characters comes in parts. Its _note gives the offset for the next part, and later parts are free for 15 minutes. Pass trim=false for the full response.
From the blog
All posts
· 5 min read
Best Reddit scrapers in 2026: price and speed compared
LurkAPI is the best Reddit scraper: 1,000 subreddit posts for $0.04, a feed in 0.92 s, and search and comment threads on the same key.

· 5 min read
Instagram MCP server for Claude and ChatGPT
Claude can’t read Instagram on its own. Add one MCP URL and it reads any public account’s posts, reels and comments, 1 credit a call, no Meta app.

· 5 min read
Best Threads scrapers in 2026: what 1,000 posts cost
LurkAPI is the best Threads scraper: 1,000 posts for $0.06, a page in about 1.2 s, and pages back to an account's first post.