SteerAds
Google AdsTutorielOptimisation

Automação Google Ads API em Python: guia iniciantes

Primeiro setup da Google Ads API em Python: OAuth2, consultas GAQL, mutations, gestão de erros e retry logic. Aqui estão exatamente os snippets para colar, com um repo GitHub público para fazer fork e começar em menos de 30 minutos.

Matt
MattTracking & Data Lead
···11 min de leitura

A API Google Ads suporta 10.000 operações por dia por conta cliente em Basic Access e um timeout de 1 hora máx por consulta, contra 30 minutos de CPU e 50 scripts máx para Google Ads Scripts (documentação oficial de cotas da API). Nas contas observadas nos benchmarks públicos do Google Ads, as equipes de data que migram de Scripts para API em Python recuperam 5 a 12 horas por semana nos pipelines de reporting e auditoria, e desbloqueiam casos de uso impossíveis em Scripts (BigQuery sync, batch mutations 5.000+ entidades, integração CRM bidirecional).

Este guia é um setup passo a passo em Python para iniciantes. Aqui está exatamente o comando para colar, o snippet OAuth que funciona de primeira, a primeira consulta GAQL, e o repo GitHub público para fazer fork e começar. Sem teoria de marketing, sem "descubra as possibilidades infinitas da API" — código que roda. Pré-requisitos: Python 3.9+, uma conta Google Ads, 30 minutos. Se você já está confortável com Google Ads Scripts, leia primeiro nosso guia dos 10 scripts Google Ads prontos para copiar que estabelece as bases de automação que a API vai expandir. Nosso calculador de desperdício de budget estima o R$ queimado/mês por broad sem negativos ou bounce LP excessivo.

Por que a API Google Ads quando Scripts já existe?

Google Ads Scripts é poderoso mas limitado: 30 minutos de CPU máx por execução, 50 scripts ativos máx por conta, JavaScript ES5 (sem npm packages), sem acesso a libs externas científicas (numpy, pandas, scikit-learn). A API Google Ads é o nível acima: Python, Java, Go, .NET ou Ruby à escolha, integração possível com qualquer stack de dados (BigQuery, Snowflake, Airflow, dbt), batch operations, async, scaling vertical e horizontal sem limite pelo Google.

O critério de transição é simples. Fique com Scripts se: você monitora 1 conta ou um MCC restrito, suas automações cabem em 30 minutos de runtime, você não precisa de libs Python externas, sua equipe não quer manter infra. Migre para a API se: você gerencia 10+ contas para pilotar em paralelo, você sincroniza com um data warehouse (BigQuery, Snowflake), você integra um CRM (HubSpot, Salesforce) bidirecional, ou você expõe as operações Google Ads em um produto interno (dashboard, automation tool).

O sweet spot operacional: use Scripts para monitoramento single-account e automações simples (alerta de budget, negativos auto), migre para a API para pipelines de dados, multi-account, integração CRM. Nas contas observadas nos benchmarks públicos do Google Ads, aproximadamente 30 a 40% das estruturas maduras (>500k EUR/ano de spend) combinam os dois: Scripts para o tático diário, API para o estratégico semanal e os data syncs.

Uma pergunta recorrente em treinamentos: "já tenho 4-5 Scripts rodando, preciso migrar tudo?" A resposta pragmática é não. A migração para a API só é rentável se você ganha em capacidade (multi-account, data warehouse, libs científicas) ou se perde tempo com as limitações de Scripts (timeout 30 min, sem pandas). Para uma conta mid-size com Scripts funcionando bem, manter o código existente e adicionar a API apenas nos novos casos de uso é a estratégia que minimiza o risco. Os dois pipelines coexistem sem conflito: Scripts executa pelo Google, sua API roda na infraestrutura, eles não se sobrepõem.

Outro trade-off frequentemente esquecido diz respeito ao custo total de posse. Scripts é gratuito em infra (Google hospeda), mas exige JavaScript em uma sandbox limitada — portanto horas de dev para contornar as limitações. A API exige Python, uma infra (Cloud Run, Lambda, EC2, ou simples cron em VPS), monitoramento, secrets management, e dependências para manter. Em 12 meses, um setup API Python para 3-5 scripts custa tipicamente entre 300 e 1.200 EUR de infra cloud, mais 20 a 60 horas de dev/manutenção. Acima de 10 scripts ou 20 contas para pilotar, o ROI se inclina claramente para a API.

Setup do ambiente Python: OAuth2, credentials, library

O setup tem 6 etapas: criar um projeto GCP, gerar as credentials OAuth2, recuperar o developer_token Google Ads, instalar a library, gerar o refresh_token, testar com uma consulta GAQL. Conte 30 minutos para tudo. Aqui está exatamente o procedimento.

Etapa 1 — Projeto GCP e ativação da API

Em console.cloud.google.com, criar um novo projeto (nome livre, ex: google-ads-api-prod). Em APIs and Services > Library, buscar "Google Ads API" e clicar Enable. A API é gratuita, mas requer a ativação explícita por projeto GCP.

Etapa 2 — Credentials OAuth2 (tipo Desktop app)

Em APIs and Services > Credentials, clicar Create Credentials > OAuth client ID. Tipo: Desktop app. Dar um nome explícito (ex: google-ads-api-cli). Baixar o JSON, guardar client_id e client_secret. Esses dois valores alimentarão o google-ads.yaml.

Etapa 3 — Developer token no Google Ads

No Google Ads, Tools and Settings > API Center. Se ainda não foi feito, solicitar um developer_token. O token inicial fica em modo Test (15.000 ops/dia, apenas contas test). Para passar para Basic Access (10.000 ops/dia, contas prod), enviar uma solicitação com descrição do caso de uso — Google retorna em 1 a 5 dias úteis. Para Standard Access (ilimitado), conte 2 a 4 semanas de revisão.

Etapa 4 — Instalar library e gerar refresh_token

Instalação da library oficial:

# Python 3.9+ necessário
pip install google-ads
# Verificar a versão (24.0.0+ corresponde à API v17)
pip show google-ads

Para gerar o refresh_token, o meio mais simples é usar o script de auth oficial fornecido pelo Google:

# Clonar os exemplos oficiais
git clone https://github.com/googleads/google-ads-python.git
cd google-ads-python/examples/authentication

# Executar o script de geração
python generate_user_credentials.py \
  --client_id YOUR_CLIENT_ID \
  --client_secret YOUR_CLIENT_SECRET

O script abre uma página OAuth no seu navegador. Valide o acesso à conta Google Ads. O script imprime um refresh_token no formato 1//0g...XXXXX. Copie-o imediatamente, ele só será exibido uma vez.

Etapa 5 — Configurar o google-ads.yaml

Criar um arquivo google-ads.yaml na raiz do projeto:

# google-ads.yaml — ADICIONAR AO .gitignore IMEDIATAMENTE
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 pai sem hífens
use_proto_plus: true

O login_customer_id é o ID do seu MCC pai sem hífens (ex: 123-456-7890 vira 1234567890). É a conta sob a qual a API autentica cada consulta. Se você consulta uma conta cliente desse MCC, você especificará o customer_id da conta cliente na própria consulta.

Segurança de credentials crítica :

Adicione google-ads.yaml ao .gitignore ANTES do primeiro commit. Um refresh_token vazado no GitHub público é detectado por bots em menos de 30 minutos e pode ser usado para gastar budget na sua conta. Em produção, carregar o YAML a partir de um secret manager (AWS Secrets Manager, GCP Secret Manager, Vault) — nunca em plain text no servidor.

Etapa 6 — Testar o setup com uma consulta simples

# test_setup.py
from google.ads.googleads.client import GoogleAdsClient

CUSTOMER_ID = "1112223333"  # ID conta cliente (não o 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()

Execute python test_setup.py. Se você vir 5 nomes de campanhas exibidos, o setup está correto. Se receber erro INVALID_CUSTOMER_ID, verifique o formato (10 dígitos sem hífens). Se receber erro NOT_ADS_USER, o refresh_token está vinculado a uma conta Google que não tem acesso ao customer_id especificado.

Primeira consulta GAQL: campaign performance

GAQL (Google Ads Query Language) é a linguagem de consulta da API Google Ads. Sintaxe próxima do SQL mas com especificidades: sem JOIN explícito (os recursos são aninhados), fields hierarquizados (campaign.id, metrics.clicks, segments.date), e um DURING para date ranges em vez de WHERE date BETWEEN.

Aqui está um script completo que puxa a performance das campanhas ENABLED nos últimos 30 dias, com impressions, clicks, cost, conversions, CTR, CPC, CPA: Nosso calculador de CPA em 2 inputs retorna o valor + mediana Brasil para seu 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']}")

Três pontos críticos sobre esta consulta. Primeiro: os costs estão em micros (1 EUR = 1.000.000 micros). Sempre dividir por 1_000_000 para obter o EUR. Segundo: metrics.ctr é um float entre 0 e 1, multiplique por 100 para ter uma porcentagem. Terceiro: a cláusula DURING LAST_30_DAYS é equivalente a WHERE segments.date BETWEEN '2026-03-28' AND '2026-04-26' mas muito mais legível. A lista das constantes DURING: TODAY, YESTERDAY, LAST_7_DAYS, LAST_30_DAYS, LAST_90_DAYS, THIS_MONTH, LAST_MONTH, etc. (lista completa).

Três armadilhas suplementares frequentemente encontradas no início da prática GAQL. *Armadilha 1: sem SELECT . A API exige declarar cada campo explicitamente. Listar 25 fields manualmente é verboso mas é intencional — Google quer limitar a largura de banda e forçar o anunciante a saber o que consome. Manter uma constante Python CAMPAIGN_FIELDS = [...] reutilizável evita reescrever a lista a cada script. Armadilha 2: segments.date introduz sempre row-fanning. Uma consulta sem segments.date agrega sobre todo o período do DURING; com segments.date, você obtém uma linha por campanha por dia, portanto 30x mais rows. Escolher conscientemente segundo a necessidade (totais do período vs série temporal). Armadilha 3: ORDER BY é obrigatório para paginação consistente. A API pagina automaticamente além de 10.000 rows; sem ORDER BY explícito, a ordem das páginas não é garantida e você corre o risco de perder entidades durante um processamento batch.

Para testar outras consultas GAQL interativamente sem Python, o Google fornece um GAQL Query Builder na documentação oficial — é o meio mais rápido de iterar sobre a estrutura da consulta antes de codificá-la. Uma dica prática: prototipar a consulta no query builder, copiar-colar no seu script Python, e só então adicionar o mapeamento para suas colunas BigQuery ou pandas. Evitar reescrever a consulta três vezes durante o debug porque esqueceu um campo.

Mutations: criar, atualizar, pausar uma campanha

As mutations são as operações de escrita da API: criar uma campanha, modificar um budget, pausar um keyword, adicionar um negativo. Elas passam pelos serviços dedicados (CampaignService, CampaignBudgetService, KeywordPlanService, etc.) e usam o pattern operation > mutation > response.

Aqui está um script que pausa uma campanha pelo seu 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")

    # Construção do resource_name (formato obrigatório)
    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 (especificar o que está sendo atualizado)
    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)

O pattern crítico para TODAS as mutations: resource_name + update_mask. O resource_name identifica a entidade (customers/{customer_id}/campaigns/{campaign_id}), o update_mask especifica quais campos estão sendo modificados (sem ele, a API retorna INVALID_FIELD_MASK). O protobuf_helpers.field_mask(None, campaign._pb) gera automaticamente o mask a partir dos fields modificados.

Para criar uma nova campanha (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  # criar em PAUSED, ativar após revisão
    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}")

Boas práticas para mutations:

  • Sempre criar em PAUSED no início, ativar manualmente após revisão. Uma campanha criada em ENABLED por engano pode gastar o budget em poucas horas.
  • Logar o resource_name retornado pela API para rastreabilidade.
  • Wrapper em try/except GoogleAdsException sistematicamente (veja seção retry).
  • Testar em conta test antes de tocar na prod. A API não oferece modo dry-run nativo (ao contrário de Scripts).

Gestão de erros e retry logic em produção

A API Google Ads pode retornar 3 categorias de erros: transitórios (RESOURCE_EXHAUSTED, DEADLINE_EXCEEDED, UNAVAILABLE) que justificam um retry com backoff, client errors (INVALID_ARGUMENT, NOT_FOUND, PERMISSION_DENIED) que nunca terão sucesso com retry, e rate limit (TOO_MANY_REQUESTS) que exigem esperar a próxima janela.

Aqui está um wrapper de retry exponencial para colar em todos os scripts de produção:

# 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 que faz retry automaticamente com exponential backoff
    em erros transitórios da 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 se erro é retryable (rate limit notavelmente)
                    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:
                        # Erro de cliente não retryable
                        for error in e.failure.errors:
                            print(f"[ERROR] {error.error_code}: {error.message}")
                        raise

            return None

        return wrapper
    return decorator

# Uso
@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))

O pattern: exponential backoff (delay dobra a cada retry, limitado a 60 segundos) + classificação dos erros (retryable vs não-retryable). Nunca fazer retry em INVALID_ARGUMENT — é um bug no seu código, não um problema de rede. Nunca fazer retry indefinidamente — 3 a 5 retries máx, senão você mascara um problema estrutural (token revogado, cota esgotada definitivamente).

Para logging estruturado, usar logging padrão do Python com formato JSON para ingestão em uma stack de observabilidade (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)

# Uso
logger.info("Pulled 247 campaigns", extra={"customer_id": CUSTOMER_ID})
Insight chave :

Nas contas observadas nos benchmarks públicos do Google Ads, os pipelines API sem retry logic falham em média 2 a 4 vezes por mês por erros transitórios de rede ou rate limit, contra 0 a 1 vez por trimestre com um wrapper retry + backoff. É o investimento de 30 linhas de código que transforma um script de "demo" em "prod-ready".

OAuth refresh flow: o que se esquece em produção

O refresh_token do Google Ads não expira desde que seja usado pelo menos uma vez a cada 6 meses. No entanto, ele pode ser revogado em vários casos a conhecer. Primeiro caso: o usuário Google que gerou o token modifica sua senha ou MFA — todos os refresh_tokens vinculados a essa conta se tornam inválidos. Segundo caso: o Google detecta comportamento suspeito (token usado de 30 IPs diferentes em 1 hora, por exemplo), revoga automaticamente e envia email de segurança. Terceiro caso: ultrapassar o limite de 50 refresh_tokens ativos simultaneamente por OAuth client dispara um FIFO silencioso que invalida os mais antigos. É por isso que se recomenda uma conta de serviço técnica dedicada à API (nunca a conta Google pessoal de um funcionário), com senha estável e MFA hardware key.

O pattern de retry em erro de auth deve ser diferente do retry transitório de rede. Se você receber UNAUTHENTICATED ou PERMISSION_DENIED relacionado ao token, NUNCA faça retry com backoff — o token não vai ressuscitar. Em vez disso, disparar um alerta (PagerDuty, Slack ops) e deixar um humano regenerar o refresh_token via script generate_user_credentials.py. Confundir esses dois casos pode custar horas de debug e conversões perdidas. Para limitar surpresas, monitorar a saúde do token com um cron simples: uma consulta GAQL minimalista (SELECT customer.id FROM customer LIMIT 1) a cada 6 horas, alerta se falha auth.

Rate limiting: entendendo as cotas na prática

As cotas da API são estruturadas em dois níveis: operações por dia (Test 15.000, Basic 10.000 por conta cliente, Standard ilimitado oficial mas throttle a partir de aproximadamente 1 milhão/dia) e consultas por segundo (limite soft em torno de 50 RPS sustentado por OAuth client, abaixo disso a API responde sem erro). Uma operação = uma linha mutada, um get, ou uma página de resultados GAQL. Uma consulta GAQL que retorna 5.000 rows conta como 1 operação, não 5.000.

A armadilha mais frequente em produção: batch mutations que ultrapassam 5.000 operações em uma única chamada. A API retorna então RESOURCE_EXHAUSTED não por causa da cota diária mas de um limite por-call de 5.000 operações. O pattern correto é chunkar as listas: se você tem 12.000 negative keywords para adicionar, faça 3 chamadas de 4.000 keywords em vez de uma só de 12.000. O chunking adiciona 5 linhas de código e evita 90% dos erros em bulk operations.

# Pattern de chunking para 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")

Para pipelines batch noturnos, adicionar um time.sleep(0.5) entre chunks suaviza a carga sem degradar o throughput global. Nas contas observadas nos benchmarks públicos do Google Ads, um batch de 50.000 mutations chunkeadas a 4k + sleep 500ms roda em aproximadamente 8-10 minutos contra 4-5 minutos em chunks 4k sem sleep — mas com taxa de falha dividida por 5. O tradeoff tempo/confiabilidade vale amplamente o sleep.

6 scripts Python prontos para fork

Para acelerar o início, publicamos um repo GitHub público github.com/steerads/google-ads-python-starter com 6 scripts Python documentados, prontos para fork. Cada script é autônomo, configurado via variáveis de ambiente, com retry logic incluída. Aqui está a lista com um snippet de exemplo para cada um.

Script 1 — Pull campaign performance LAST_30_DAYS

Recupera os KPIs completos (impressions, clicks, cost, conversions, ROAS) das campanhas ENABLED, exporta em CSV ou envia para BigQuery. Frequência: diária ou horária. Veja snippet completo na seção 3.

Script 2 — Update campaign budget em bulk

Modifica os budgets diários de N campanhas em uma única batch operation. Útil para rebalanceamentos mensais de budget ou ajustes sazonais automáticos.

# 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

Puxa o Search Term Performance Report de 30 dias, identifica as consultas com 0 conversão com mais de 15 clicks, adiciona como negativos no nível da campanha. Equivalente ao script 2 do nosso guia Scripts 10 ready-to-copy, mas via API para tratar 100+ campanhas em uma única execução.

Script 4 — Pause underperforming keywords (CTR + CPA)

Identifica keywords com CTR inferior a 1% E CPA superior a 2x o CPA target nos últimos 30 dias, pausa automaticamente com log antes da ação. Critério multidimensional impossível em Scripts (que força iterar sobre AdsApp.keywords()), trivial em GAQL.

Script 5 — Export report to BigQuery (data warehousing)

Puxa as métricas agregadas, transforma com pandas, carrega no BigQuery via google-cloud-bigquery. Este é o caso de uso em que a API supera Scripts: impossibilidade de conectar Scripts ao BigQuery de forma adequada, enquanto em Python são 10 linhas.

# 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

Combina pull performance + verificação de anomalias (spend, CPA, conversions) + push Slack via webhook se anomalia detectada. Equivalente industrializado de um cron job de monitoramento diário.

# 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)

O repo github.com/steerads/google-ads-python-starter contém para cada script: o código completo documentado, um README com setup passo a passo, um arquivo .env.example para as variáveis, e um requirements.txt fixado para reprodutibilidade. Para automações cross-platform, veja também nosso guia n8n Google Ads automation flows e nosso guia MCP Google Ads + Claude Desktop para pilotar Google Ads em modo conversacional pelo Claude.

Quando migrar de Scripts para API

A migração de Scripts para API não é um upgrade sistemático — é uma escolha de tradeoff. Aqui estão os 5 gatilhos concretos observados nos benchmarks públicos em auditoria.

Gatilho 1: multi-account (10+ contas para pilotar em paralelo). Scripts é mono-conta por essência (um script vinculado a uma conta ou um MCC). Acima de 10 contas para sincronizar, a API Python com um loop for na lista de customer_ids se torna bem mais simples de manter.

Gatilho 2: data warehousing. Se você quer enviar as métricas para BigQuery, Snowflake, Redshift, a API é obrigatória. Scripts UrlFetchApp pode tecnicamente chamar uma API REST, mas a autenticação GCP, o batching, o ETL com pandas — isso exige Python.

Gatilho 3: integração CRM bidirecional. Para push offline conversions a partir de Salesforce/HubSpot, a API Google Ads suporta OfflineUserDataJobService que faz upload batch seguro. Veja nosso guia de conversion tracking Google Ads para o framework funcional.

Gatilho 4: necessidades ML / estatísticas avançadas. Detecção de anomalias estatísticas (ARIMA, Prophet), segmentação de keywords (clustering), forecasting de spend — todas essas tarefas exigem numpy/pandas/scikit-learn, impossíveis em Scripts.

Gatilho 5: produto interno. Você está construindo um dashboard ou ferramenta interna que exibe/manipula dados do Google Ads. Obrigatoriamente API — Scripts não pode expor uma UI ou responder a uma requisição HTTP.

Para os 5 casos inversos (conta única, monitoramento simples, sem necessidade de dados, equipe não-tech, infra limitada), Scripts continua superior em custo/manutenção. Nosso guia Microsoft Ads Scripts cobre o equivalente no lado Microsoft.

Para contas que querem industrializar sem codificar sua própria stack, nosso módulo Auto-otimização cobre o equivalente dos 6 scripts acima em modo gerenciado: pull performance multi-account, anomaly detection, Slack alerts, BigQuery sync, sem uma linha de Python para manter. Veja também nosso checklist de auditoria Google Ads para a base de auditoria que deve preceder toda automação, e nosso comparativo Zapier vs Make para as automações no-code complementares.

Erros comuns a evitar ao começar com a API

Cinco erros recorrentes atrasam os iniciantes Python na API Google Ads. Cada um custa em média várias horas de debug evitáveis se o problema for conhecido de antemão. Aqui está a lista com diagnóstico e correção direta.

1. Confundir customer_id e login_customer_id. Diagnóstico: o script retorna INVALID_CUSTOMER_ID ou NOT_ADS_USER quando tudo parece bem configurado. Correção: login_customer_id (no YAML) = ID do MCC pai sob o qual a API se autentica, sem hífens. customer_id (na consulta) = ID da conta cliente que você quer consultar, sem hífens. Se você consulta diretamente uma conta que não está sob um MCC, coloque o ID dela nos dois lugares. Sempre remover os hífens formato 123-456-7890 -> 1234567890.

2. Esquecer o update_mask nas mutations. Diagnóstico: a mutation falha com MISSING_REQUIRED_FIELD: update_mask ou todos os campos da entidade são substituídos por seus defaults (catástrofe em prod). Correção: para toda operação update, gerar o mask via protobuf_helpers.field_mask(None, entity._pb) após setar os campos. O mask declara à API quais campos você modifica; sem ele, a API ou recusa, ou interpreta como "todos os campos estão no valor padrão" e reseta tudo.

3. Fazer loop em pull individual em vez de batch. Diagnóstico: um script que precisa puxar 500 campanhas leva 25 minutos em vez de 30 segundos. Correção: NUNCA fazer for campaign_id in ids: ga_service.search(...) com uma consulta por campanha. Em vez disso, uma única consulta GAQL que filtra WHERE campaign.id IN (...) ou puxa tudo e filtra pelo Python. A API não é penalizada pelo tamanho da consulta, ela é penalizada pelo número de chamadas.

4. Testar diretamente em prod sem conta test. Diagnóstico: um bug de arredondamento no código de mutation de budget reduz por engano todos os budgets para 1 EUR. A prod queima. Correção: criar uma conta test Google Ads (gratuita, sem cartão associado) para todas as mutations em dev. O developer_token Test cobre apenas contas test — o que é uma proteção involuntária bem-vinda. Só passar para Basic Access após validar o código em sandbox em 2-3 casos de uso reais.

5. Ignorar a paginação GAQL além de 10.000 rows. Diagnóstico: um script que puxa keywords de uma conta grande trava além de 10k rows ou retorna incompleto sem erro. Correção: usar ga_service.search_stream(...) que pagina automaticamente sem carregar em memória todo o resultado. Para pulls de 50.000+ rows, é obrigatório — search() carrega tudo em memória e travará em máquinas com pouca RAM.

Para a documentação oficial, veja o portal Google Ads API e o repo oficial google-ads-python que contém dezenas de exemplos mantidos pelo Google.

Fontes

Fontes oficiais consultadas para este guia:

FAQ

É necessário um developer token para usar a API Google Ads?

Sim, é obrigatório. O developer token está vinculado a uma conta MCC (manager) e é obtido via Tools and Settings > API Center no Google Ads. O token inicial começa em modo Test (limitado a 15.000 operações/dia, apenas contas test), depois Basic Access (10.000 operações/dia, contas prod) sob solicitação, depois Standard Access (ilimitado) após revisão pelo Google. Conte de 1 a 5 dias úteis para Basic, 2 a 4 semanas para Standard com um dossiê que detalhe seu caso de uso. Para começar, o token Test é mais que suficiente: testar o OAuth, escrever suas primeiras consultas GAQL, debugar as mutations em uma conta test. A passagem para Basic só acontece após validação do código em sandbox.

Qual a diferença entre google-ads-python (oficial) e googleads (legacy)?

google-ads-python é a library oficial moderna que consome a API REST/gRPC v17+ (versões 2024-2026). googleads era a library legacy que consumia a API SOAP v201809 (deprecated desde 2022, totalmente desativada no final de 2023). Se você encontrar código googleads ou AdWords API em fóruns, é obsoleto. Para 2026, use APENAS google-ads-python (pip install google-ads), versão 24.0.0 mínima que corresponde à API v17. A library expõe um client GoogleAdsClient inicializado a partir de YAML ou env vars, com serviços tipados (CampaignService, KeywordService, GoogleAdsService para consultas GAQL). Documentação oficial em developers.google.com/google-ads/api/docs/client-libs/python.

Quantas consultas GAQL por segundo a API suporta?

As cotas dependem do tier do seu developer token. Test = 15.000 operações/dia total, Basic = 10.000 operações/dia por conta cliente, Standard = sem cota oficial mas throttling pelo Google em caso de abuso. Uma operação = uma consulta GAQL ou uma mutation. Para as contas observadas nos benchmarks públicos do Google Ads, o padrão dominante é 200 a 800 consultas/dia para um script de monitoramento diário em uma conta mid-size. O rate limit por hora é 10.000 consultas/hora máx por OAuth client. Além disso, a API retorna um RESOURCE_EXHAUSTED que deve ser tratado com exponential backoff (veja seção retry logic). Para um script que precisa fazer 5.000+ mutations, preveja batch processing com sleep entre os batches.

A API pode gerenciar Smart Bidding e Performance Max?

Sim, a API expõe todas as funcionalidades do Google Ads, incluindo as mais recentes (Performance Max, Smart Bidding, Demand Gen). Para Performance Max, você interage com CampaignService (campaignType=PERFORMANCE_MAX), AssetGroupService (os grupos de assets PMax) e ConversionGoalService. Para Smart Bidding, está nos campos bidding_strategy das campanhas (TARGET_CPA, TARGET_ROAS, MAXIMIZE_CONVERSIONS). Limitação: a criação de campanha PMax via API requer todos os assets em paralelo (imagens, headlines, descriptions, sitelinks), o que torna o código mais complexo que a criação Search. Para começar, prefira criar/modificar campanhas Search ou Shopping e passe para PMax após estabilizar o pipeline. Veja nosso guia Performance Max para a estratégia de asset group.

Como proteger as credentials OAuth em produção?

Três princípios: NUNCA commitar o google-ads.yaml ou os refresh_tokens no Git (adicione-os ao .gitignore), use secret managers (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault) para armazenamento em prod, e faça rotação dos refresh_tokens periodicamente (90 dias máx recomendado). Para um setup local de dev, o YAML no home directory é OK. Para prod, carregar o YAML a partir do secret manager na inicialização da aplicação. Os developer_token, client_id, client_secret podem ficar em env vars; o refresh_token deve ficar em secret manager estrito. Se um refresh_token vazar (commit no GitHub por acidente), revogue-o imediatamente via Google Cloud Console > APIs and Services > Credentials, regenere um novo, e audite os logs de API para detectar eventuais chamadas maliciosas.

Ready to optimize your campaigns?

Start a free audit in 2 minutes and discover the ROI potential of your accounts.

Start my free audit

Free audit — no credit card required

Keep reading