L'API Google Ads supporte 10 000 operations par jour par compte client en Basic Access et un timeout de 1 heure max par requete, contre 30 minutes CPU et 50 scripts max pour Google Ads Scripts (documentation officielle quotas API). Sur les comptes observés dans les benchmarks Google Ads publics, les equipes data qui passent de Scripts a API en Python recuperent 5 a 12 heures par semaine sur les pipelines de reporting et d'audit, et debloquent des cas d'usage impossibles cote Scripts (BigQuery sync, batch mutations 5 000+ entites, integration CRM bidirectionnelle).
Ce guide est un setup pas-a-pas Python pour debutants. Voici exactement la commande a coller, le snippet OAuth qui marche du premier coup, la premiere requete GAQL, et le repo GitHub public a forker pour demarrer. Pas de theorie marketing, pas de "decouvrez les possibilites infinies de l'API" — du code qui tourne. Prerequis : Python 3.9+, un compte Google Ads, 30 minutes. Si vous etes deja a l'aise avec Google Ads Scripts, lisez d'abord notre guide des 10 scripts Google Ads prets a coller qui pose les bases d'automation que l'API va etendre. Notre calculateur de gaspillage budget estime le € brûlé/mois par broad sans négatifs ou bounce LP excessif.
Pourquoi l'API Google Ads quand Scripts existe deja ?
Google Ads Scripts est puissant mais bride : 30 minutes CPU max par run, 50 scripts actifs max par compte, JavaScript ES5 (pas de npm packages), pas d'acces a des libs externes scientifiques (numpy, pandas, scikit-learn). L'API Google Ads est l'echelon au-dessus : Python, Java, Go, .NET ou Ruby au choix, integration possible avec n'importe quelle stack data (BigQuery, Snowflake, Airflow, dbt), batch operations, async, scaling vertical et horizontal sans limite cote Google.
Le critere de bascule est simple. Restez sur Scripts si : vous monitorez 1 compte ou un MCC restreint, vos automations tiennent en 30 minutes de runtime, vous n'avez pas besoin de libs Python externes, votre equipe ne veut pas maintenir d'infra. Passez a l'API si : vous gerez 10+ comptes a piloter en parallele, vous synchronisez avec un data warehouse (BigQuery, Snowflake), vous integrez un CRM (HubSpot, Salesforce) en bidirectionnel, ou vous exposez les operations Google Ads dans un produit interne (dashboard, automation tool).
Le sweet spot operationnel : utilisez Scripts pour le monitoring single-account et les automations simples (alerte budget, negatifs auto), passez a l'API pour les pipelines data, le multi-account, l'integration CRM. Sur les comptes observés dans les benchmarks Google Ads publics, environ 30 a 40% des structures matures (>500k EUR/an de spend) combinent les deux : Scripts pour le tactique daily, API pour le strategique hebdo et les data syncs.
Une question revient souvent en formation : "j'ai deja 4-5 Scripts qui tournent, est-ce que je dois tout migrer ?" La reponse pragmatique est non. La migration vers l'API n'est rentable que si vous gagnez en capacite (multi-account, data warehouse, libs scientifiques) ou si vous perdez du temps sur les limitations Scripts (timeout 30 min, pas de pandas). Pour un compte mid-size avec des Scripts qui tournent bien, garder le code en place et ajouter l'API uniquement sur les nouveaux cas d'usage est la strategie qui minimise le risque. Les deux pipelines coexistent sans conflit : Scripts s'execute cote Google, votre API tourne cote infrastructure, ils ne se marchent pas dessus.
L'autre arbitrage souvent oublie concerne le cout total de possession. Scripts est gratuit cote infra (Google heberge), mais demande du JavaScript dans une sandbox limitee — donc des heures dev pour contourner les limitations. L'API demande du Python, une infra (Cloud Run, Lambda, EC2, ou simple cron sur VPS), du monitoring, des secrets management, et des dependencies a maintenir. Sur 12 mois, un setup API Python pour 3-5 scripts coute typiquement entre 300 et 1 200 EUR d'infra cloud, plus 20 a 60 heures de dev/maintenance. Au-dela de 10 scripts ou 20 comptes a piloter, le ROI bascule clairement vers l'API.
Setup environnement Python : OAuth2, credentials, library
Le setup tient en 6 etapes : creer un projet GCP, generer les credentials OAuth2, recuperer le developer_token Google Ads, installer la library, generer le refresh_token, tester avec une requete GAQL. Comptez 30 minutes pour le tout. Voici exactement la procedure.
Etape 1 — Projet GCP et activation API
Sur console.cloud.google.com, creer un nouveau projet (nom libre, ex : google-ads-api-prod). Dans APIs and Services > Library, rechercher "Google Ads API" et cliquer Enable. L'API est gratuite, mais necessite l'activation explicite par projet GCP.
Etape 2 — Credentials OAuth2 (Desktop app type)
Dans APIs and Services > Credentials, cliquer Create Credentials > OAuth client ID. Type : Desktop app. Donner un nom explicite (ex : google-ads-api-cli). Telecharger le JSON, conserver client_id et client_secret. Ces deux valeurs alimenteront le google-ads.yaml.
Etape 3 — Developer token cote Google Ads
Dans Google Ads, Tools and Settings > API Center. Si pas deja fait, demander un developer_token. Le token initial est en mode Test (15 000 ops/jour, comptes test only). Pour passer en Basic Access (10 000 ops/jour, comptes prod), soumettre une demande avec description du cas d'usage — Google revient sous 1 a 5 jours ouvres. Pour Standard Access (illimite), comptez 2 a 4 semaines de revue.
Etape 4 — Install library et generer refresh_token
Installation de la library officielle :
# Python 3.9+ requis
pip install google-ads
# Verifier la version (24.0.0+ correspond a l'API v17)
pip show google-ads
Pour generer le refresh_token, le moyen le plus simple est d'utiliser le script d'auth officiel fourni par Google :
# Cloner les exemples officiels
git clone https://github.com/googleads/google-ads-python.git
cd google-ads-python/examples/authentication
# Lancer le script de generation
python generate_user_credentials.py \
--client_id YOUR_CLIENT_ID \
--client_secret YOUR_CLIENT_SECRET
Le script ouvre une page OAuth dans votre navigateur. Validez l'acces au compte Google Ads. Le script imprime un refresh_token au format 1//0g...XXXXX. Copiez-le immediatement, il ne s'affichera qu'une fois.
Etape 5 — Configurer le google-ads.yaml
Creer un fichier google-ads.yaml a la racine du projet :
# google-ads.yaml — A AJOUTER AU .gitignore IMMEDIATEMENT
developer_token: "YOUR_DEVELOPER_TOKEN_22_CHARS"
client_id: "YOUR_CLIENT_ID.apps.googleusercontent.com"
client_secret: "GOCSPX-YOUR_CLIENT_SECRET"
refresh_token: "1//0gYOUR_REFRESH_TOKEN"
login_customer_id: "1234567890" # MCC parent sans tirets
use_proto_plus: true
Le login_customer_id est l'ID de votre MCC parent sans tirets (ex : 123-456-7890 devient 1234567890). C'est le compte sous lequel l'API authentifie chaque requete. Si vous interrogez un compte client de ce MCC, vous specifierez le customer_id du compte client dans la requete elle-meme.
Ajoutez google-ads.yaml au .gitignore AVANT le premier commit. Un refresh_token leak sur GitHub public est detecte par les bots en moins de 30 minutes et peut etre utilise pour facturer du spend sur votre compte. En production, charger le YAML depuis un secret manager (AWS Secrets Manager, GCP Secret Manager, Vault) — jamais en plain text sur le serveur.
Etape 6 — Tester le setup avec une requete simple
# test_setup.py
from google.ads.googleads.client import GoogleAdsClient
CUSTOMER_ID = "1112223333" # ID compte client (pas le MCC)
def test_connection():
client = GoogleAdsClient.load_from_storage("google-ads.yaml")
ga_service = client.get_service("GoogleAdsService")
query = """
SELECT campaign.id, campaign.name, campaign.status
FROM campaign
LIMIT 5
"""
response = ga_service.search(customer_id=CUSTOMER_ID, query=query)
for row in response:
print(f"{row.campaign.id} | {row.campaign.name} | {row.campaign.status.name}")
if __name__ == "__main__":
test_connection()
Lancer python test_setup.py. Si vous voyez 5 noms de campagnes affiches, le setup est bon. Si erreur INVALID_CUSTOMER_ID, verifiez le format (10 chiffres sans tirets). Si erreur NOT_ADS_USER, le refresh_token est lie a un compte Google qui n'a pas acces au customer_id specifie.
Premiere requete GAQL : campaign performance
GAQL (Google Ads Query Language) est le langage de requete de l'API Google Ads. Syntaxe proche du SQL mais avec des specificites : pas de JOIN explicite (les ressources sont nestees), des fields hierarchises (campaign.id, metrics.clicks, segments.date), et un DURING pour les date ranges au lieu de WHERE date BETWEEN.
Voici un script complet qui pull la performance des campagnes ENABLED sur les 30 derniers jours, avec impressions, clicks, cost, conversions, CTR, CPC, CPA : Notre calculateur CPA en 2 inputs retourne la valeur + médiane France pour votre vertical.
# pull_campaign_performance.py
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
CUSTOMER_ID = "1112223333"
def pull_performance(client, customer_id):
ga_service = client.get_service("GoogleAdsService")
query = """
SELECT
campaign.id,
campaign.name,
campaign.status,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions,
metrics.conversions_value,
metrics.ctr,
metrics.average_cpc
FROM campaign
WHERE campaign.status = 'ENABLED'
AND segments.date DURING LAST_30_DAYS
ORDER BY metrics.cost_micros DESC
LIMIT 50
"""
try:
response = ga_service.search(customer_id=customer_id, query=query)
results = []
for row in response:
cost_eur = row.metrics.cost_micros / 1_000_000 # micros -> EUR
cpa = cost_eur / row.metrics.conversions if row.metrics.conversions > 0 else None
results.append({
"id": row.campaign.id,
"name": row.campaign.name,
"impressions": row.metrics.impressions,
"clicks": row.metrics.clicks,
"cost_eur": round(cost_eur, 2),
"conversions": row.metrics.conversions,
"ctr_pct": round(row.metrics.ctr * 100, 2),
"cpc_eur": round(row.metrics.average_cpc / 1_000_000, 2),
"cpa_eur": round(cpa, 2) if cpa else None,
})
return results
except GoogleAdsException as ex:
print(f"Request failed: {ex.error.code().name}")
for error in ex.failure.errors:
print(f" - {error.message}")
raise
if __name__ == "__main__":
client = GoogleAdsClient.load_from_storage("google-ads.yaml")
perf = pull_performance(client, CUSTOMER_ID)
for c in perf:
print(f"{c['name'][:40]:40s} | "
f"{c['impressions']:>8,} impr | "
f"{c['clicks']:>5,} clicks | "
f"{c['cost_eur']:>7,.2f} EUR | "
f"{c['conversions']:>5.1f} conv | "
f"CPA: {c['cpa_eur']}")
Trois points critiques sur cette requete. Premierement : les costs sont en micros (1 EUR = 1 000 000 micros). Toujours diviser par 1_000_000 pour avoir l'EUR. Deuxiemement : metrics.ctr est un float entre 0 et 1, multipliez par 100 pour avoir un pourcentage. Troisiemement : la clause DURING LAST_30_DAYS est equivalente a WHERE segments.date BETWEEN '2026-03-28' AND '2026-04-26' mais bien plus lisible. La liste des constantes DURING : TODAY, YESTERDAY, LAST_7_DAYS, LAST_30_DAYS, LAST_90_DAYS, THIS_MONTH, LAST_MONTH, etc. (liste complete).
Trois pieges supplementaires souvent rencontres en debut de pratique GAQL. *Piege 1 : pas de SELECT . L'API impose de declarer chaque champ explicitement. Lister 25 fields a la main est verbeux mais c'est volontaire — Google veut limiter la bande passante et forcer l'annonceur a savoir ce qu'il consomme. Maintenir une constante Python CAMPAIGN_FIELDS = [...] reutilisable evite de reecrire la liste a chaque script. Piege 2 : segments.date introduit toujours du row-fanning. Une requete sans segments.date agrege sur toute la periode du DURING ; avec segments.date, vous obtenez une ligne par campagne par jour, donc 30x plus de rows. Choisir consciemment selon le besoin (totaux periode vs serie temporelle). Piege 3 : ORDER BY est obligatoire pour le pagination consistente. L'API pagine automatiquement au-dela de 10 000 rows ; sans ORDER BY explicite, l'ordre des pages n'est pas garanti et vous risquez de manquer des entites lors d'un traitement batch.
Pour tester d'autres requetes GAQL en interactif sans Python, Google fournit un GAQL Query Builder dans la documentation officielle — c'est le moyen le plus rapide d'iterer sur la structure de la requete avant de la coder. Une astuce pratique : prototyper la requete dans le query builder, copier-coller dans votre script Python, et seulement ensuite ajouter le mapping vers vos colonnes BigQuery ou pandas. Eviter de reecrire la requete trois fois pendant qu'on debug parce qu'on a oublie un champ.
Mutations : creer, mettre a jour, pauser une campagne
Les mutations sont les operations d'ecriture de l'API : creer une campagne, modifier un budget, pauser un keyword, ajouter un negatif. Elles passent par les services dedies (CampaignService, CampaignBudgetService, KeywordPlanService, etc.) et utilisent le pattern operation > mutation > response.
Voici un script qui pause une campagne par son ID :
# pause_campaign.py
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
CUSTOMER_ID = "1112223333"
CAMPAIGN_ID = "111222333"
def pause_campaign(client, customer_id, campaign_id):
campaign_service = client.get_service("CampaignService")
# Construction du resource_name (format obligatoire)
resource_name = campaign_service.campaign_path(customer_id, campaign_id)
# Operation : update
campaign_operation = client.get_type("CampaignOperation")
campaign = campaign_operation.update
campaign.resource_name = resource_name
campaign.status = client.enums.CampaignStatusEnum.PAUSED
# Field mask (specifier ce qu'on update)
client.copy_from(
campaign_operation.update_mask,
protobuf_helpers.field_mask(None, campaign._pb),
)
try:
response = campaign_service.mutate_campaigns(
customer_id=customer_id,
operations=[campaign_operation],
)
print(f"Paused campaign: {response.results[0].resource_name}")
except GoogleAdsException as ex:
for error in ex.failure.errors:
print(f"Error: {error.message}")
if error.location:
for field in error.location.field_path_elements:
print(f" Field: {field.field_name}")
raise
if __name__ == "__main__":
from google.api_core import protobuf_helpers
client = GoogleAdsClient.load_from_storage("google-ads.yaml")
pause_campaign(client, CUSTOMER_ID, CAMPAIGN_ID)
Le pattern critique pour TOUTES les mutations : resource_name + update_mask. Le resource_name identifie l'entite (customers/{customer_id}/campaigns/{campaign_id}), le update_mask specifie quels champs on modifie (sans, l'API renvoie INVALID_FIELD_MASK). Le protobuf_helpers.field_mask(None, campaign._pb) genere automatiquement le mask depuis les fields modifies.
Pour creer une nouvelle campagne (Search Standard, daily budget 100 EUR, Manual CPC) :
# create_search_campaign.py
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
import uuid
CUSTOMER_ID = "1112223333"
def create_campaign_budget(client, customer_id, daily_budget_eur):
budget_service = client.get_service("CampaignBudgetService")
operation = client.get_type("CampaignBudgetOperation")
budget = operation.create
budget.name = f"Budget {uuid.uuid4().hex[:8]}"
budget.amount_micros = int(daily_budget_eur * 1_000_000)
budget.delivery_method = client.enums.BudgetDeliveryMethodEnum.STANDARD
response = budget_service.mutate_campaign_budgets(
customer_id=customer_id, operations=[operation]
)
return response.results[0].resource_name
def create_search_campaign(client, customer_id, name, budget_resource):
campaign_service = client.get_service("CampaignService")
operation = client.get_type("CampaignOperation")
campaign = operation.create
campaign.name = name
campaign.advertising_channel_type = client.enums.AdvertisingChannelTypeEnum.SEARCH
campaign.status = client.enums.CampaignStatusEnum.PAUSED # creer en PAUSED, activer apres review
campaign.manual_cpc.enhanced_cpc_enabled = False
campaign.campaign_budget = budget_resource
# Network settings
campaign.network_settings.target_google_search = True
campaign.network_settings.target_search_network = True
campaign.network_settings.target_content_network = False
campaign.network_settings.target_partner_search_network = False
response = campaign_service.mutate_campaigns(
customer_id=customer_id, operations=[operation]
)
return response.results[0].resource_name
if __name__ == "__main__":
client = GoogleAdsClient.load_from_storage("google-ads.yaml")
budget_rn = create_campaign_budget(client, CUSTOMER_ID, daily_budget_eur=100)
print(f"Budget created: {budget_rn}")
campaign_rn = create_search_campaign(
client, CUSTOMER_ID, "Test API Campaign", budget_rn
)
print(f"Campaign created (PAUSED): {campaign_rn}")
Bonnes pratiques cote mutations :
- Toujours creer en PAUSED au depart, activer manuellement apres revue. Une campagne creee en ENABLED par erreur peut depenser le budget en quelques heures.
- Logger le resource_name retourne par l'API pour traceabilite.
- Wrapper dans try/except GoogleAdsException systematiquement (voir section retry).
- Tester sur un compte test avant de toucher la prod. L'API ne propose pas de mode dry-run natif (a l'inverse de Scripts).
Gestion d'erreurs et retry logic en production
L'API Google Ads peut renvoyer 3 categories d'erreurs : transitoires (RESOURCE_EXHAUSTED, DEADLINE_EXCEEDED, UNAVAILABLE) qui justifient un retry avec backoff, client errors (INVALID_ARGUMENT, NOT_FOUND, PERMISSION_DENIED) qui ne se re-tenteront jamais avec succes, et rate limit (TOO_MANY_REQUESTS) qui demandent d'attendre la fenetre suivante.
Voici un wrapper de retry exponentiel a coller dans tous les scripts production :
# retry_helpers.py
import time
from functools import wraps
from google.ads.googleads.errors import GoogleAdsException
from google.api_core.exceptions import (
DeadlineExceeded, ServiceUnavailable, ResourceExhausted
)
RETRYABLE_ERRORS = (
DeadlineExceeded,
ServiceUnavailable,
ResourceExhausted,
)
def with_retry(max_retries=3, base_delay=2.0, max_delay=60.0):
"""
Decorator qui retry automatiquement avec exponential backoff
sur les erreurs transitoires de l'API Google Ads.
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries + 1):
try:
return func(*args, **kwargs)
except RETRYABLE_ERRORS as e:
if attempt == max_retries:
print(f"[RETRY] Max retries reached: {e}")
raise
delay = min(base_delay * (2 ** attempt), max_delay)
print(f"[RETRY] Attempt {attempt + 1}/{max_retries} "
f"failed: {type(e).__name__}. Retry in {delay}s...")
time.sleep(delay)
except GoogleAdsException as e:
# Check si erreur retryable (rate limit notamment)
is_retryable = any(
err.error_code.quota_error or
err.error_code.internal_error
for err in e.failure.errors
)
if is_retryable and attempt < max_retries:
delay = min(base_delay * (2 ** attempt), max_delay)
print(f"[RETRY] GoogleAdsException retryable. "
f"Retry in {delay}s...")
time.sleep(delay)
else:
# Erreur client non retryable
for error in e.failure.errors:
print(f"[ERROR] {error.error_code}: {error.message}")
raise
return None
return wrapper
return decorator
# Usage
@with_retry(max_retries=3, base_delay=2.0)
def pull_performance_safe(client, customer_id):
ga_service = client.get_service("GoogleAdsService")
query = "SELECT campaign.id, campaign.name FROM campaign LIMIT 100"
return list(ga_service.search(customer_id=customer_id, query=query))
Le pattern : exponential backoff (delay double a chaque retry, plafonne a 60 secondes) + classification des erreurs (retryable vs non-retryable). Ne jamais retry sur INVALID_ARGUMENT — c'est un bug dans votre code, pas un probleme de reseau. Ne jamais retry indefiniment — 3 a 5 retries max, sinon vous masquez un probleme structurel (token revoked, quota epuise definitivement).
Pour le logging structure, utiliser logging standard Python avec un format JSON pour ingestion dans un stack observability (Datadog, Grafana Loki) :
import logging
import json
class JsonFormatter(logging.Formatter):
def format(self, record):
log_obj = {
"ts": self.formatTime(record),
"level": record.levelname,
"msg": record.getMessage(),
"module": record.module,
}
if record.exc_info:
log_obj["exc"] = self.formatException(record.exc_info)
return json.dumps(log_obj)
logger = logging.getLogger("google_ads_api")
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# Usage
logger.info("Pulled 247 campaigns", extra={"customer_id": CUSTOMER_ID})
Sur les comptes observés dans les benchmarks Google Ads publics, les pipelines API sans retry logic plantent en moyenne 2 a 4 fois par mois sur des erreurs transitoires reseau ou rate limit, contre 0 a 1 fois par trimestre avec un wrapper retry + backoff. C'est l'investissement de 30 lignes de code qui passe un script de "demo" a "prod-ready".
OAuth refresh flow : ce qu'on oublie en production
Le refresh_token Google Ads n'expire pas tant qu'il est utilise au moins une fois tous les 6 mois. En revanche il peut etre revoque dans plusieurs cas a connaitre. Premier cas : l'utilisateur Google qui a genere le token modifie son mot de passe ou sa MFA — tous les refresh_tokens lies a ce compte deviennent invalides. Deuxieme cas : Google detecte un comportement suspect (token utilise depuis 30 IPs differentes en 1 heure, par exemple), revoke automatiquement et envoie un email de securite. Troisieme cas : depasser la limite de 50 refresh_tokens actifs simultanes par client OAuth declenche un FIFO silencieux qui invalide les plus anciens. C'est pour cette raison qu'on recommande un compte de service technique dedie a l'API (jamais le compte Google personnel d'un employe), avec mot de passe stable et MFA hardware key.
Le pattern de retry sur erreur d'auth doit etre different du retry transitoire reseau. Si vous recevez UNAUTHENTICATED ou PERMISSION_DENIED lie au token, ne JAMAIS retry avec backoff — le token ne va pas ressuciter. A la place, declencher une alerte (PagerDuty, Slack ops) et laisser un humain regenerer le refresh_token via le script generate_user_credentials.py. Confondre ces deux cas peut couter des heures de debug et des conversions perdues. Pour limiter les surprises, monitorer la sante du token avec un cron simple : une requete GAQL minimaliste (SELECT customer.id FROM customer LIMIT 1) toutes les 6 heures, alerte si echec auth.
Rate limiting : comprendre les quotas en pratique
Les quotas API sont structures en deux niveaux : operations par jour (Test 15 000, Basic 10 000 par client account, Standard illimite officiel mais throttle a partir d'environ 1 million/jour) et requetes par seconde (limite molle autour de 50 RPS soutenu par OAuth client, en dessous l'API renvoie sans erreur). Une operation = une ligne mutee, un get, ou une page de resultats GAQL. Une requete GAQL qui retourne 5 000 rows compte pour 1 operation, pas 5 000.
Le piege le plus frequent en production : les batch mutations qui depassent 5 000 operations dans un seul appel. L'API renvoie alors RESOURCE_EXHAUSTED non a cause du quota journalier mais d'une limite par-call de 5 000 operations. Le pattern correct est de chunker les listes : si vous avez 12 000 negative keywords a ajouter, faites 3 appels de 4 000 keywords plutot qu'un seul de 12 000. Le chunking ajoute 5 lignes de code et evite 90% des erreurs en bulk operations.
# Chunking pattern pour batch mutations
def chunked(iterable, size=4000):
for i in range(0, len(iterable), size):
yield iterable[i:i + size]
for chunk in chunked(all_operations, size=4000):
response = service.mutate_negatives(
customer_id=customer_id, operations=chunk
)
print(f"Chunk processed: {len(response.results)} mutations")
Pour les pipelines batch nightly, ajouter un time.sleep(0.5) entre chunks lisse la charge sans degrader le throughput global. Sur les comptes observés dans les benchmarks Google Ads publics, un batch de 50 000 mutations chunkees a 4k + sleep 500ms tourne en environ 8-10 minutes contre 4-5 minutes en chunks 4k sans sleep — mais avec un taux d'echec divise par 5. Le tradeoff temps/fiabilite vaut largement le sleep.
6 scripts Python prets a fork
Pour accelerer le demarrage, nous publions un repo GitHub public github.com/steerads/google-ads-python-starter avec 6 scripts Python documentes, prets a forker. Chaque script est autonome, configure via variables d'env, avec retry logic incluse. Voici la liste avec un snippet d'exemple pour chacun.
Script 1 — Pull campaign performance LAST_30_DAYS
Recupere les KPI complets (impressions, clicks, cost, conversions, ROAS) des campagnes ENABLED, exporte en CSV ou pousse en BigQuery. Frequence : daily ou hourly. Voir snippet complet section 3.
Script 2 — Update campaign budget en bulk
Modifie les budgets quotidiens de N campagnes en une seule batch operation. Utile pour les rebalancing budget mensuels ou les ajustements saisonniers automatiques.
# bulk_update_budgets.py
def bulk_update_budgets(client, customer_id, updates):
"""
updates = [{"budget_id": "111", "new_amount_eur": 150}, ...]
"""
service = client.get_service("CampaignBudgetService")
operations = []
for u in updates:
op = client.get_type("CampaignBudgetOperation")
budget = op.update
budget.resource_name = service.campaign_budget_path(
customer_id, u["budget_id"]
)
budget.amount_micros = int(u["new_amount_eur"] * 1_000_000)
client.copy_from(
op.update_mask,
protobuf_helpers.field_mask(None, budget._pb),
)
operations.append(op)
response = service.mutate_campaign_budgets(
customer_id=customer_id, operations=operations
)
return [r.resource_name for r in response.results]
Script 3 — Add negative keywords from search query report
Pull le Search Term Performance Report sur 30 jours, identifie les requetes a 0 conversion avec plus de 15 clicks, ajoute en negatives au niveau campagne. Equivalent du script 2 de notre guide Scripts 10 ready-to-copy, mais en API pour traiter 100+ campagnes en une seule run.
Script 4 — Pause underperforming keywords (CTR + CPA)
Identifie les keywords avec CTR inferieur a 1% ET CPA superieur a 2x le CPA target sur 30 jours, les pause automatiquement avec un log avant action. Critere multi-dimensionnel impossible cote Scripts (qui force a iterer sur AdsApp.keywords()), trivial en GAQL.
Script 5 — Export report to BigQuery (data warehousing)
Pull les metrics agregees, transforme avec pandas, charge dans BigQuery via google-cloud-bigquery. C'est le cas d'usage pour lequel l'API surpasse Scripts : impossibilite de connecter Scripts a BigQuery proprement, alors que cote Python c'est 10 lignes.
# bigquery_export.py
from google.cloud import bigquery
import pandas as pd
def export_to_bigquery(client, customer_id, dataset_id, table_id):
# 1. Pull GAQL data
perf = pull_performance(client, customer_id)
df = pd.DataFrame(perf)
df["snapshot_date"] = pd.Timestamp.today().normalize()
# 2. BigQuery client
bq = bigquery.Client()
table_ref = f"{bq.project}.{dataset_id}.{table_id}"
# 3. Load with append
job_config = bigquery.LoadJobConfig(
write_disposition=bigquery.WriteDisposition.WRITE_APPEND,
autodetect=True,
)
job = bq.load_table_from_dataframe(df, table_ref, job_config=job_config)
job.result() # wait for completion
print(f"Loaded {len(df)} rows to {table_ref}")
Script 6 — Daily monitoring with Slack alerts
Combine pull performance + check anomalies (spend, CPA, conversions) + push Slack via webhook si anomalie detectee. Equivalent industrialise d'un cron job de monitoring quotidien.
# daily_monitoring.py
import requests
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
def detect_anomalies(perf):
alerts = []
for c in perf:
# Spend > 2x avg 7d
if c.get("cost_eur", 0) > c.get("avg_cost_7d", 0) * 2:
alerts.append(f"Spend +100% : {c['name']}")
# CPA > 3x target
if c.get("cpa_eur") and c.get("target_cpa") and \
c["cpa_eur"] > c["target_cpa"] * 3:
alerts.append(f"CPA x3 target : {c['name']}")
return alerts
def send_slack(alerts):
if not alerts:
return
msg = "Google Ads anomalies detected:\n" + "\n".join(f"- {a}" for a in alerts)
requests.post(SLACK_WEBHOOK, json={"text": msg})
if __name__ == "__main__":
client = GoogleAdsClient.load_from_storage("google-ads.yaml")
perf = pull_performance(client, CUSTOMER_ID)
alerts = detect_anomalies(perf)
send_slack(alerts)
Le repo github.com/steerads/google-ads-python-starter contient pour chaque script : le code complet documente, un README avec setup pas a pas, un fichier .env.example pour les variables, et un requirements.txt epingle pour reproductibilite. Pour les automation cross-platform, voir aussi notre guide n8n Google Ads automation flows et notre guide MCP Google Ads + Claude Desktop pour piloter Google Ads en conversational depuis Claude.
Quand passer de Scripts a API
Le passage de Scripts a API n'est pas un upgrade systematique — c'est un choix de tradeoff. Voici les 5 declencheurs concrets observés dans les benchmarks publics en audit.
Declencheur 1 : multi-account (10+ comptes a piloter en parallele). Scripts est mono-compte par essence (un script attache a un compte ou un MCC). Au-dela de 10 comptes a synchroniser, l'API Python avec une boucle for sur la liste des customer_ids devient bien plus simple a maintenir.
Declencheur 2 : data warehousing. Si vous voulez pousser les metrics dans BigQuery, Snowflake, Redshift, l'API est obligatoire. Scripts UrlFetchApp peut techniquement appeler une API REST, mais l'authentification GCP, le batching, l'ETL avec pandas — ca demande Python.
Declencheur 3 : integration CRM bidirectionnelle. Pour push offline conversions depuis Salesforce/HubSpot, l'API Google Ads supporte OfflineUserDataJobService qui fait du upload batch securise. Voir notre guide conversion tracking Google Ads pour le cadre fonctionnel.
Declencheur 4 : besoins ML / stats avances. Detection d'anomalies statistiques (ARIMA, Prophet), segmentation de keywords (clustering), forecasting de spend — toutes ces taches demandent numpy/pandas/scikit-learn, impossibles en Scripts.
Declencheur 5 : produit interne. Vous construisez un dashboard ou un outil interne qui affiche/manipule les data Google Ads. Forcement API — Scripts ne peut pas exposer une UI ou repondre a une requete HTTP.
Pour les 5 cas inverses (compte unique, monitoring simple, pas de besoin data, equipe non-tech, infra contrainte), Scripts reste superieur en cout/maintenance. Notre guide Microsoft Ads Scripts couvre l'equivalent cote Microsoft.
Pour les comptes qui veulent industrialiser sans coder leur propre stack, notre module Auto-optimisation couvre l'equivalent des 6 scripts ci-dessus en mode managed : pull performance multi-account, anomaly detection, Slack alerts, BigQuery sync, sans une ligne de Python a maintenir. Voir aussi notre checklist d'audit Google Ads pour la base d'audit qui doit preceder toute automation, et notre comparatif Zapier vs Make pour les automations no-code complementaires.
Erreurs courantes a eviter en debutant l'API
Cinq erreurs recurrentes ralentissent les debutants Python sur l'API Google Ads. Chacune coute en moyenne plusieurs heures de debug evitables si on connait le piege a l'avance. Voici la liste avec diagnostic et correction directe.
1. Confondre customer_id et login_customer_id. Diagnostic : le script renvoie INVALID_CUSTOMER_ID ou NOT_ADS_USER alors que tout semble bien configure. Correction : login_customer_id (dans le YAML) = ID du MCC parent sous lequel s'authentifie l'API, sans tirets. customer_id (dans la requete) = ID du compte client que vous voulez interroger, sans tirets. Si vous interrogez directement un compte qui n'est pas sous un MCC, mettez son ID dans les deux endroits. Toujours retirer les tirets format 123-456-7890 -> 1234567890.
2. Oublier le update_mask sur les mutations. Diagnostic : la mutation echoue avec MISSING_REQUIRED_FIELD: update_mask ou bien tous les champs de l'entite sont remplaces par leurs defaults (catastrophe en prod). Correction : pour toute operation update, generer le mask via protobuf_helpers.field_mask(None, entity._pb) apres avoir set les champs. Le mask declare a l'API quels champs vous modifiez ; sans, l'API soit refuse, soit interprete comme "tous les champs sont a leur valeur par defaut" et reset tout.
3. Boucler en pull individuel au lieu de batch. Diagnostic : un script qui doit pull 500 campagnes met 25 minutes au lieu de 30 secondes. Correction : ne JAMAIS faire for campaign_id in ids: ga_service.search(...) avec une requete par campagne. A la place, une seule requete GAQL qui filtre WHERE campaign.id IN (...) ou pull tout et filtre cote Python. L'API n'est pas penalisee sur la taille de la requete, elle l'est sur le nombre d'appels.
4. Tester directement en prod sans compte test. Diagnostic : un bug d'arrondi dans le code de mutation budget ramene par erreur tous les budgets a 1 EUR. La prod brule. Correction : creer un compte test Google Ads (gratuit, sans CB associee) pour toutes les mutations en dev. Le developer_token Test couvre uniquement les comptes test — ce qui est une protection involontaire bienvenue. Ne basculer sur Basic Access qu'apres avoir valide le code en sandbox sur 2-3 cas d'usage reels.
5. Ignorer la pagination GAQL au-dela de 10 000 rows. Diagnostic : un script qui pull les keywords d'un gros compte plante au-dela de 10k rows ou retourne incomplet sans erreur. Correction : utiliser ga_service.search_stream(...) qui paginate automatiquement sans charger en memoire l'ensemble du resultat. Pour les pulls de 50 000+ rows, c'est obligatoire — search() charge tout en memoire et plantera sur les machines a faible RAM.
Pour la documentation officielle, voir le portail Google Ads API et le repo officiel google-ads-python qui contient des dizaines d'exemples maintenus par Google.
Sources
Sources officielles consultées pour ce guide :
FAQ
Faut-il un developer token pour utiliser l'API Google Ads ?
Oui, c'est obligatoire. Le developer token est lie a un compte MCC (manager) et s'obtient via Tools and Settings > API Center cote Google Ads. Le token initial demarre en mode Test (limite a 15 000 operations/jour, accounts test only), puis Basic Access (10 000 operations/jour, comptes prod) sur demande, puis Standard Access (illimite) apres revue par Google. Comptez 1 a 5 jours ouvres pour Basic, 2 a 4 semaines pour Standard avec un dossier qui detaille votre cas d'usage. Pour debuter, le token Test suffit largement : tester l'OAuth, ecrire vos premieres requetes GAQL, debugger les mutations sur un compte test. Le passage Basic n'arrive qu'apres validation du code en sandbox.
Quelle difference entre google-ads-python (officiel) et googleads (legacy) ?
google-ads-python est la library officielle moderne qui consomme l'API REST/gRPC v17+ (versions 2024-2026). googleads etait la library legacy qui consommait l'API SOAP v201809 (deprecated depuis 2022, totalement coupee fin 2023). Si vous tombez sur du code googleads ou AdWords API dans des forums, c'est obsolete. Pour 2026, utilisez UNIQUEMENT google-ads-python (pip install google-ads), version 24.0.0 minimum qui correspond a l'API v17. La library expose un client GoogleAdsClient initialise depuis YAML ou env vars, avec des services typees (CampaignService, KeywordService, GoogleAdsService pour les requetes GAQL). Documentation officielle sur developers.google.com/google-ads/api/docs/client-libs/python.
Combien de requetes GAQL par seconde l'API supporte ?
Les quotas dependent du tier de votre developer token. Test = 15 000 operations/jour total, Basic = 10 000 operations/jour par client account, Standard = pas de quota officiel mais throttling cote Google si abus. Une operation = une requete GAQL ou une mutation. Pour les comptes observés dans les benchmarks Google Ads publics, le pattern dominant est 200 a 800 requetes/jour pour un script de monitoring quotidien sur un compte mid-size. La rate limit horaire est 10 000 requetes/heure max par OAuth client. Au-dela, l'API renvoie un RESOURCE_EXHAUSTED qu'il faut traiter avec un exponential backoff (voir section retry logic). Pour un script qui doit faire 5 000+ mutations, prevoir un batch processing avec sleep entre les batches.
L'API peut-elle gerer les Smart Bidding et Performance Max ?
Oui, l'API expose toutes les fonctionnalites de Google Ads, y compris les plus recentes (Performance Max, Smart Bidding, Demand Gen). Pour Performance Max, vous interagissez avec CampaignService (campaignType=PERFORMANCE_MAX), AssetGroupService (les groupes d'assets PMax) et ConversionGoalService. Pour Smart Bidding, c'est dans les bidding_strategy fields des campagnes (TARGET_CPA, TARGET_ROAS, MAXIMIZE_CONVERSIONS). Limite : la creation de campagne PMax via API necessite tous les assets en parallele (images, headlines, descriptions, sitelinks) ce qui rend le code plus complexe que la creation Search. Pour debuter, prefere creer/modifier des campagnes Search ou Shopping et passer a PMax une fois le pipeline stabilise. Voir notre guide Performance Max pour la strategie d'asset group.
Comment securiser les credentials OAuth en production ?
Trois principes : ne JAMAIS committer le google-ads.yaml ou les refresh_tokens dans Git (ajoutez-les au .gitignore), utilisez des secret managers (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault) pour le stockage en prod, et faites tourner les refresh_tokens periodiquement (90 jours max recommande). Pour un setup local de dev, le YAML dans home directory est OK. Pour la prod, charger le YAML depuis le secret manager au demarrage de l'application. Les developer_token, client_id, client_secret peuvent etre en env vars ; le refresh_token doit etre en secret manager strict. Si un refresh_token leak (commit GitHub par accident), revoke-le immediatement via Google Cloud Console > APIs and Services > Credentials, regenere un nouveau, et audit les logs d'API pour detecter d'eventuels appels malveillants.