De Claude-Skill Die Mijn Google Ads-Teksten Schrijft
Ik bouwde een Claude-skill die rechtstreeks de Google Ads API bevraagt met GAQL, responsieve zoekadvertenties met een zwakke CTR of hoge CPA herkent, en koppen en beschrijvingen herschrijft binnen Google's exacte tekenlimieten (30 en 90). Hij maakt de nieuwe advertentie aan in status GEPAUZEERD — ik controleer en activeer zelf. Het punt dat er echt toe doet: Google's tekenlimieten zijn de werkelijke beperking, niet de tekst. Een model dat tekens niet correct telt, produceert advertenties die Google stilletjes afkapt of weigert.
Elke woensdag. 28.400+ operators. Geen opvulling.
✓ Controleer je inbox — klik op de bevestigingslink om je aanmelding te voltooien.
✓ Je bent aangemeld!
✓ Je staat al op de lijst.
[Lezing van een operator] Ik heb al een Claude-skill die mijn Facebook-advertenties beheert — die build staat hier. Google Ads was het moeilijkere vervolg, niet omdat de API slechter is, maar omdat responsieve zoekadvertenties harde tekenlimieten hebben die Facebook niet heeft: 30 tekens per kop, 90 per beschrijving. Een herschrijving die goed leest maar bij het serveren wordt geweigerd of afgekapt, is geen herschrijving — dat is een bug. Het grootste deel van het werk aan deze skill ging naar het dwingen van het model om die beperking te respecteren in plaats van erop te vertrouwen.
Table of contents
Open Table of contents
- Waarom Google Ads een andere build nodig had dan Facebook
- API-toegang krijgen (het deel dat iedereen onderschat)
- De bestandsstructuur van de skill
- De centrale TypeScript-code
- Hoe ik hem dagelijks gebruik
- Wat de validator niet opvangt
- De tools die ik hiervoor gebruik
- Veelgestelde vragen
- Het oordeel van de operator
Waarom Google Ads een andere build nodig had dan Facebook
De Facebook-skill leest prestatiedata, herschrijft tekst en publiceert een nieuwe advertentieset. Google Ads doet dezelfde drie taken, maar twee dingen verschillen:
- De querytaal is GAQL, geen REST-filters. De Google Ads API v18 draait op Google Ads Query Language — SQL-achtig, alleen-lezen, precies gebouwd voor dit type rapportage-extractie.
- Responsieve zoekadvertenties hebben harde limieten. Tot 15 koppen (30 tekens elk) en 4 beschrijvingen (90 tekens elk) per advertentie. Google waarschuwt niet zachtjes — het weigert het asset bij indiening, of erger, serveert een afgekapte versie die verkeerd leest voor een echte klant.
Punt 2 is wat naïeve implementaties breekt. Vraag een model om “een pakkendere kop te schrijven” en het levert vrolijk 34 tekens op. Google Ads weigert dat asset of toont het midden in een woord afgekapt. Ik merk dat pas als ik een live advertentie zie die eindigt op “Boek je afspra—”.
API-toegang krijgen (het deel dat iedereen onderschat)
Drie dingen, en het derde kost je een week als je het niet plant:
- Een Google Cloud-project met OAuth2-inloggegevens — client ID, client secret en een refresh token met de scope
https://www.googleapis.com/auth/adwords. Genereer het refresh token eenmalig, via de OAuth playground of een kort lokaal script; daarna is het een opgeslagen credential, geen loginflow meer. - Een developer token, aangevraagd via je Google Ads-beheerdersaccount (MCC) onder Beheer → API Center.
- Het toegangsniveau. Elk nieuw developer token begint met testaccount-toegang — het werkt alleen tegen Google Ads-testaccounts, niet tegen je echte account. Je vraagt Basic Access aan (tot 15.000 bewerkingen/dag, genoeg voor dit doel) via een formulier dat Google handmatig beoordeelt. Reken op een paar werkdagen voor goedkeuring voordat je live wilt gaan op een echt account.
Bewaar GOOGLE_ADS_CLIENT_ID, GOOGLE_ADS_CLIENT_SECRET, GOOGLE_ADS_REFRESH_TOKEN, GOOGLE_ADS_DEVELOPER_TOKEN en GOOGLE_ADS_CUSTOMER_ID (het 10-cijferige accountnummer, zonder streepjes) in je .env. Beheer je het account via een MCC, dan heb je ook GOOGLE_ADS_LOGIN_CUSTOMER_ID nodig, ingesteld op het ID van het beheerdersaccount — zonder dat worden queries afgewezen met een permissiefout, en de foutmelding zegt niet waarom.
De bestandsstructuur van de skill
.claude/skills/google-ads/
SKILL.md ← instructies die Claude leest
index.ts ← de daadwerkelijke tool-implementatie
limits.ts ← validatie van tekenlimietenSKILL.md:
# Google Ads Manager Skill
Use this skill when the user says "check my Google Ads", "run ads
report", or "rewrite underperforming RSAs". Never run this without
explicit user instruction — it touches live ad spend.
## What it can do
- Pull campaign and ad group performance via GAQL (last 7 or 30 days)
- Flag responsive search ads with CTR < 3% or CPA above the stored
target as underperformers
- Rewrite headlines (≤30 chars) and descriptions (≤90 chars) for
flagged ads in Ale's voice
- Create new ad variations as PAUSED — never enabled automatically
## What it will NOT do
- Change budgets or bids without explicit confirmation
- Enable a paused ad
- Submit any headline or description that fails the character checkDe laatste regel onder “What it will NOT do” is degene die ik toevoegde nadat de skill in de eerste week een kop van 32 tekens publiceerde. De eigen telling van het model klopte niet, twee tekens te veel — dichtbij genoeg om bij een vluchtige blik door te glippen, ver genoeg om het asset geweigerd te krijgen. Sindsdien loopt alles door een harde validator, niet door de zelfrapportage van het model.
De centrale TypeScript-code
// .claude/skills/google-ads/index.ts
import { GoogleAdsApi } from "google-ads-api";
import Anthropic from "@anthropic-ai/sdk";
const client = new GoogleAdsApi({
client_id: process.env.GOOGLE_ADS_CLIENT_ID!,
client_secret: process.env.GOOGLE_ADS_CLIENT_SECRET!,
developer_token: process.env.GOOGLE_ADS_DEVELOPER_TOKEN!,
});
const customer = client.Customer({
customer_id: process.env.GOOGLE_ADS_CUSTOMER_ID!,
login_customer_id: process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID,
refresh_token: process.env.GOOGLE_ADS_REFRESH_TOKEN!,
});
interface AdPerformance {
adGroupAdId: string;
campaignName: string;
headlines: string[];
descriptions: string[];
impressions: number;
clicks: number;
costMicros: number;
conversions: number;
ctr: number;
cpa: number;
}
async function getAdPerformance(days = 7): Promise<AdPerformance[]> {
const rows = await customer.query(`
SELECT
ad_group_ad.ad.id,
campaign.name,
ad_group_ad.ad.responsive_search_ad.headlines,
ad_group_ad.ad.responsive_search_ad.descriptions,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions
FROM ad_group_ad
WHERE segments.date DURING LAST_${days === 30 ? "30" : "7"}_DAYS
AND ad_group_ad.status = 'ENABLED'
`);
return rows.map((r: any) => {
const impressions = Number(r.metrics.impressions ?? 0);
const clicks = Number(r.metrics.clicks ?? 0);
const cost = Number(r.metrics.cost_micros ?? 0) / 1_000_000;
const conversions = Number(r.metrics.conversions ?? 0);
return {
adGroupAdId: r.ad_group_ad.ad.id,
campaignName: r.campaign.name,
headlines: (r.ad_group_ad.ad.responsive_search_ad?.headlines ?? []).map((h: any) => h.text),
descriptions: (r.ad_group_ad.ad.responsive_search_ad?.descriptions ?? []).map((d: any) => d.text),
impressions,
clicks,
costMicros: Number(r.metrics.cost_micros ?? 0),
conversions,
ctr: impressions > 0 ? (clicks / impressions) * 100 : 0,
cpa: conversions > 0 ? cost / conversions : Infinity,
};
});
}
const HEADLINE_MAX = 30;
const DESCRIPTION_MAX = 90;
function validateAsset(text: string, max: number): boolean {
return text.length <= max;
}
async function rewriteRsa(
ad: AdPerformance,
targetCpa: number,
): Promise<{ headlines: string[]; descriptions: string[] }> {
const anthropic = new Anthropic();
const msg = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{
role: "user",
content: `Rewrite this Google Ads responsive search ad in Alejandro Rioja's voice: direct, operator-focused, specific. It's underperforming — CTR ${ad.ctr.toFixed(2)}%, CPA $${ad.cpa.toFixed(2)} vs a $${targetCpa} target.
Current headlines: ${JSON.stringify(ad.headlines)}
Current descriptions: ${JSON.stringify(ad.descriptions)}
Rules, non-negotiable:
- Each headline: 30 characters MAXIMUM, including spaces and punctuation
- Each description: 90 characters MAXIMUM, including spaces and punctuation
- Return 5 headlines and 4 descriptions
- Return JSON only: {"headlines": ["...", ...], "descriptions": ["...", ...]}`,
}],
});
const text = (msg.content[0] as any).text.replace(/```json\n?/, "").replace(/```/, "").trim();
const parsed = JSON.parse(text);
const headlines = parsed.headlines.filter((h: string) => validateAsset(h, HEADLINE_MAX));
const descriptions = parsed.descriptions.filter((d: string) => validateAsset(d, DESCRIPTION_MAX));
if (headlines.length < 3 || descriptions.length < 2) {
throw new Error("Model returned too many over-limit assets — rerun or revise manually.");
}
return { headlines, descriptions };
}Elke kop en beschrijving wordt in code tegen de limiet gecontroleerd, na generatie, voordat er iets de API raakt. De herschrijving van het model is een concept. De validator is het filter.
Hoe ik hem dagelijks gebruik
> controleer mijn Google Ads van de laatste 7 dagenClaude voert de GAQL-query uit, markeert alles onder 3% CTR of boven mijn opgeslagen CPA-doel, en vraagt om bevestiging voor hij herschrijft. Ik bevestig. Hij genereert nieuwe koppen en beschrijvingen, laat ze door de tekenvalidator lopen, verwerpt wat niet slaagt, en maakt via de mutate-operatie van de Google Ads API een nieuwe advertentie aan in status PAUSED. Ik vergelijk de nieuwe tekst met de oude binnen Google Ads, activeer de versies die ik echt zou draaien, en laat de rest gepauzeerd.
Zelfde plafond als de Facebook-versie: ongeveer 20 minuten in plaats van een hele middag in de interface, omdat het mechanische deel — data ophalen, schrijven binnen een harde beperking, de wijziging klaarzetten — precies is wat een model voor mij zou moeten doen.
Wat de validator niet opvangt
Tekens tellen is mechanisch. Relevantie is dat niet. Een kop kan 28 tekens tellen, elke check doorstaan, en toch de verkeerde boodschap zijn voor de zoekterm waaraan hij gekoppeld is. De skill kan niet weten of een slecht presterende advertentiegroep een zwak aanbod heeft, een mismatch met de landingspagina, of oprecht zwakke tekst — hij kan me alleen vertellen dat de tekst zwak is relatief aan de advertenties ernaast. Als alle advertenties in een campagne een lage CTR hebben, is dat een targeting- of landingspaginaprobleem, en meer herschrijfcycli aan de skill geven op een verkeerd gematchte zoekintentie levert alleen braver, even genegeerd tekstwerk op.
Ik lees de GAQL-output zelf voor ik een batch herschrijvingen goedkeur, net als bij Facebook. De skill neemt het typen en tellen van tekens van me over. Hij neemt het oordeel over wat er echt kapot is niet over.
De tools die ik hiervoor gebruik
Claude draait achter beide advertentieskills — die voor Facebook en deze — ze delen hetzelfde herschrijf-en-valideerpatroon, alleen tegen een andere API en een strakker tekenbudget.
Google Ads API (npm-pakket google-ads-api) — de TypeScript-wrapper om de ruwe gRPC/REST-API. Hij regelt de OAuth-refresh en het opbouwen van GAQL-queries zodat je geen protobuf met de hand hoeft te knutselen.
Veelgestelde vragen
Heb ik Basic Access nodig, of werkt testaccount-toegang?
Testaccount-toegang werkt alleen tegen Google Ads-testaccounts — nuttig voor ontwikkeling, nutteloos voor een echt account met echte uitgaven. Je hebt Basic Access nodig (aan te vragen in het API Center van je beheerdersaccount) voordat deze skill een live campagne kan aanraken. Goedkeuring is handmatige beoordeling, niet instant, dus vraag het aan voordat je het nodig hebt, niet de week dat je wilt lanceren.
Waarom 5 koppen en 4 beschrijvingen in plaats van de maxima van 15 en 4?
Google staat tot 15 koppen toe, maar zowel Ad Strength als praktijktests suggereren afnemende meeropbrengst voorbij 8–10 echt verschillende invalshoeken — de meeste accounts eindigen met meerdere bijna-identieke koppen die alleen het aantal opvullen. Ik beperk de output van het model tot 5 nieuwe koppen per herschrijfbatch zodat ik echt verschillende opties beoordeel, niet negen variaties van dezelfde zin filter.
Wat gebeurt er als de herschrijving van het model bij elke poging de tekencontrole niet doorstaat?
De validator gooit een fout in plaats van stilletjes een te lang asset af te kappen of in te dienen. In de praktijk betekent dit dat ik de prompt één keer opnieuw laat lopen, en als het twee keer mislukt, schrijf ik die specifieke kop zelf met de hand. Dat gebeurt vaker bij beschrijvingen — 90 tekens is een strakkere pasvorm voor een volledige zin dan het lijkt.
Ook relevant: voor alleen-lezen rapportage over GA4, Search Console, Bing, Ahrefs en Core Web Vitals in één Claude-connector in plaats van per platform een eigen skill te bouwen, gebruik ik SiteWide MCP — hetzelfde idee om Claude aan echte marketingdata te koppelen, maar met een breder bereik dan één advertentieplatform.
Het oordeel van de operator
Als je zelf met de hand varianten van responsieve zoekadvertenties schrijft en tekens telt, doe je precies het soort mechanische, regelgebonden taak dat een model zou moeten overnemen. Het deel dat echt beoordelingsvermogen vraagt — of het een tekst- of een targetingprobleem is — blijft van jou. Bouw de skill, laat hem schrijven en valideren tegen Google’s limieten, en houd de diagnose voor jezelf.
Elke woensdag. 28.400+ operators. Geen opvulling.
✓ Controleer je inbox — klik op de bevestigingslink om je aanmelding te voltooien.
✓ Je bent aangemeld!
✓ Je staat al op de lijst.
Gerelateerde berichten
Geplande taken in Claude Cowork: de handleiding
Geplande taken in Claude Cowork maken van een eenmalige prompt een terugkerende klus die vanzelf afgaat, zonder code of server. Zo zet je er een goed op.
AI AgentsAI-agentdiensten die bureaus in 2026 kunnen verkopen
Zes AI-agent-automatiseringen uit mijn eigen bedrijf, omgevormd tot dienstonderdelen die een bureau kan afbakenen, prijzen en verkopen aan klanten.
AI AgentsAI-agents voor SaaS: wat je eerst automatiseert
Het niveau- en ROI-framework voor AI-agentbeslissingen, toegepast op de SaaS-workflows die het echt verdienen om als eerste geautomatiseerd te worden.
Ontvang het AI-playbook in je inbox
Elke woensdag. 28.400+ operators. Geen opvulling.
Controleer je inbox.
We hebben je een bevestigingsmail gestuurd — klik op de link om je aanmelding te voltooien. Controleer je spam als je hem niet binnen een minuut ziet.
Je bent aangemeld.
Welkom — de volgende editie valt binnenkort in je inbox.
Je staat al op de lijst — kijk er elke woensdag naar uit.