tva
← Insights

Google Ads API: Configurazione, Autenticazione, Operazioni di Lettura e Scrittura

Una guida tecnica per configurare l'accesso programmatico a Google Ads — dall'architettura alla prima chiamata API. Copre la configurazione MCC, OAuth 2.0, i livelli del Developer Token, la libreria Python google-ads, le query GAQL in lettura e il pattern di scrittura validate_only.

Architettura

Tre entità separate sono necessarie prima che una singola chiamata API funzioni:

EntitàScopoEsempio
**Account Manager (MCC)**Rilascia il Developer Token`login_customer_id`
**Google Cloud Project**Client OAuth 2.0 per l'autenticazioneClient ID + Secret
**Account Google Ads**Target di tutte le operazioni API`customer_id`

Non si tratta dello stesso account. Un MCC può possedere un Developer Token senza possedere l'account target — deve solo essere collegato come manager. Il Google Cloud Project può appartenere a un'identità Google diversa dall'MCC. L'account target è l'account inserzionista dove risiedono le campagne.

Questa separazione è importante perché API Center (dove vengono forniti i Developer Token) è disponibile solo all'interno degli Account Manager. Un account Google Ads autonomo non può accedere ad API Center, anche se dispone di utenti amministrativi.

L'header della chiamata API assembla queste tre identità più un token Bearer OAuth:

developer-token: <da API Center dell'MCC>
login-customer-id: <ID numerico MCC, senza trattini>
customer-id: <ID numerico account target>
Authorization: Bearer <token di accesso OAuth 2.0>

Prerequisiti

  • Un Account Manager Google Ads con configurazione completata
  • L'account ads target collegato all'Account Manager e il collegamento accettato
  • Un Google Cloud Project con la Google Ads API abilitata
  • Python 3.9+ con la libreria google-ads (pip install google-ads)
  • Un browser per il flusso di consenso OAuth una tantum

Passo 1: Account Manager e Developer Token

API Center si trova in Strumenti e impostazioni → Configurazione → API Center all'interno di un Account Manager. Se API Center non è presente, l'account non è un Account Manager oppure la sua configurazione è incompleta.

Da API Center, richiedi un Developer Token. Imposta il tipo di account su Inserzionista (non Agenzia o Terze Parti) quando il token è per i tuoi account. Il token viene rilasciato immediatamente al livello Accesso di Test.

L'Accesso di Test consente:

  • Operazioni di lettura su qualsiasi account collegato
  • Operazioni di scrittura solo su account di test
  • Nessuna operazione di scrittura su account di produzione

L'errore per le scritture su account di produzione con Accesso di Test è RESOURCE_NOT_FOUND — non PERMISSION_DENIED. Questo è fuorviante perché la risorsa esiste ed è leggibile. L'errore significa che l'API rifiuta di instradare la mutation con il livello di accesso corrente.

Richiesta di Accesso Base

Per scrivere su account di produzione, richiedi l'Accesso Base dalla stessa pagina API Center. Il modulo di richiesta contiene 12 domande. Per uso interno sui propri account, le risposte pertinenti sono:

DomandaRisposta
Q4: Rapporto con un rappresentante GoogleNo
Q6: Modello di businessDescrivi il tuo caso d'uso (e-commerce, lead generation, reportistica interna)
Q8: Chi avrà accessoUtenti interni — solo dipendenti
Q9: Strumento sviluppato da terze partiNo (script/agent propri)
Q10: API Conversion Tracking e RemarketingNo (a meno che non le utilizzi effettivamente)
Q11: Tipi di campagneSearch, Performance Max, Shopping (a seconda dei casi)
Q12: FunzionalitàCreazione campagne, Gestione campagne, Reportistica

Un documento di design (PDF) è richiesto per la Q7. Deve descrivere l'architettura dell'API, il flusso di autenticazione, la strategia di rate-limiting, la gestione degli errori e il caso d'uso esclusivamente interno. Sii fattuale — il team di compliance verifica la coerenza con il caso d'uso dichiarato.

Tempo di elaborazione a giugno 2026: circa 3 giorni lavorativi, con una nota sull'alto volume di richieste superiore al normale.

Passo 2: OAuth 2.0

Configurazione del Google Cloud Project

Nella Google Cloud Console, nel progetto pertinente:

  1. Abilita la Google Ads API (API e servizi → Libreria)
  2. Configura la schermata di consenso OAuth:
  • Tipo di utente: Interno (solo gli utenti del tuo Google Workspace possono autorizzare — salta la verifica dell'app esterna)
  • Nome app, email di supporto, email di contatto sviluppatore
  1. Aggiungi lo scope in Accesso ai dati: https://www.googleapis.com/auth/adwords
  2. Crea un ID client OAuth:
  • Tipo di applicazione: App desktop
  • Scarica il JSON del client secret

Il tipo App desktop utilizza http://localhost come URI di reindirizzamento. È corretto — il flusso OAuth apre un browser, Google reindirizza a localhost con il codice di autorizzazione e un server HTTP locale lo cattura.

Generazione del Refresh Token

Uno script Python che esegue un server HTTP locale su 127.0.0.1:0 (porta casuale), stampa l'URL di autorizzazione, attende una richiesta, estrae il parametro code e lo scambia per i token:

from pathlib import Path
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
from google_auth_oauthlib.flow import Flow

class CallbackHandler(BaseHTTPRequestHandler):
    code_value = None
    def do_GET(self):
        qs = parse_qs(urlparse(self.path).query)
        CallbackHandler.code_value = (qs.get('code') or [None])[0]
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'OAuth complete.')
    def log_message(self, format, *args):
        return

server = HTTPServer(('127.0.0.1', 0), CallbackHandler)
redirect_uri = f'http://127.0.0.1:{server.server_port}/'
flow = Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/adwords'],
    redirect_uri=redirect_uri
)
auth_url, _ = flow.authorization_url(
    access_type='offline',
    prompt='consent'
)
print(auth_url)
server.handle_request()
flow.fetch_token(code=CallbackHandler.code_value)
Path('google_ads_token.json').write_text(flow.credentials.to_json())

Due parametri sono importanti:

  • access_type='offline' — restituisce un refresh token, non solo un access token
  • prompt='consent' — forza una nuova schermata di consenso anche se l'utente ha già autorizzato in precedenza; necessario per ottenere un nuovo refresh token se gli scope sono cambiati

Il refresh token è permanente a meno che non venga revocato, l'utente cambi la password o il token rimanga inutilizzato per 6 mesi.

Insidia degli Scope con Client Esistenti

Se il client OAuth è stato precedentemente autorizzato per altri scope (Gmail, Drive, Calendar), Google restituisce l'unione degli scope vecchi e nuovi. La libreria google-auth-oauthlib rifiuta questa discrepanza di scope per impostazione predefinita. Imposta la variabile d'ambiente prima di caricare il flusso:

import os
os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1'

In alternativa, ometti include_granted_scopes=true dall'URL di autorizzazione, il che indica a Google di richiedere solo lo scope esplicitamente passato.

Passo 3: `google-ads.yaml`

La libreria legge la configurazione da un file YAML:

developer_token: <valore>
client_id: <valore>
client_secret: <valore>
refresh_token: <valore>
login_customer_id: 1911599764
use_proto_plus: true

Regole di sicurezza:

  • Permessi del file: 0600 (solo lettura/scrittura proprietario)
  • Mai committare nel controllo versione
  • Conservare al di fuori di qualsiasi repository Git
  • Per configurazioni di team, distribuire tramite un secrets manager, non un file condiviso

login_customer_id è l'ID numerico dell'MCC senza trattini. Indica all'API quale Developer Token dell'account manager utilizzare. Il customer_id target viene passato al momento della chiamata, non nella configurazione.

use_proto_plus: true abilita l'interfaccia protobuf-plus, richiesta per le versioni API correnti.

Passo 4: Inizializzazione del Client Python

from google.ads.googleads.client import GoogleAdsClient

client = GoogleAdsClient.load_from_storage(
    '/path/to/google-ads.yaml',
    version='v22'
)
gs = client.get_service('GoogleAdsService')

Il parametro version seleziona esplicitamente la versione dell'API. La libreria google-ads viene distribuita con più versioni installate in parallelo (da v19 a v22 a giugno 2026). Specifica sempre la versione — il default potrebbe essere in ritardo o non supportare campi più recenti.

Verifica la connettività:

cs = client.get_service('CustomerService')
customers = cs.list_accessible_customers()
ids = [r.split('/')[-1] for r in customers.resource_names]
# Output di esempio: ['1305475941', '3977581086', '1911599764']

Passo 5: Operazioni di Lettura (GAQL)

Google Ads utilizza GAQL (Google Ads Query Language), una sintassi simile a SQL. Tutte le operazioni di lettura passano attraverso GoogleAdsService.search_stream(), che evita la paginazione ed è preferito rispetto a search() per la maggior parte delle query.

Campagne con Metriche di Performance

SELECT
  campaign.id,
  campaign.name,
  campaign.status,
  campaign.advertising_channel_type,
  campaign.serving_status,
  campaign.bidding_strategy_type,
  metrics.impressions,
  metrics.clicks,
  metrics.cost_micros,
  metrics.conversions,
  metrics.conversions_value,
  metrics.ctr,
  metrics.average_cpc
FROM campaign
ORDER BY campaign.id

Budget delle Campagne

SELECT
  campaign_budget.id,
  campaign_budget.name,
  campaign_budget.amount_micros,
  campaign_budget.delivery_method,
  campaign_budget.status,
  campaign.id,
  campaign.name
FROM campaign_budget

Gruppi di Annunci

SELECT
  ad_group.id,
  ad_group.name,
  ad_group.status,
  ad_group.cpc_bid_micros,
  campaign.id,
  campaign.name
FROM ad_group
ORDER BY campaign.id, ad_group.id

Parole Chiave (per Impression)

SELECT
  ad_group_criterion.keyword.text,
  ad_group_criterion.keyword.match_type,
  ad_group_criterion.status,
  ad_group_criterion.criterion_id,
  ad_group.id,
  ad_group.name,
  campaign.id,
  campaign.name,
  metrics.impressions,
  metrics.clicks,
  metrics.cost_micros,
  metrics.conversions
FROM keyword_view
WHERE ad_group_criterion.type = 'KEYWORD'
ORDER BY metrics.impressions DESC
LIMIT 100

Annunci con Stato delle Policy

SELECT
  ad_group_ad.ad.id,
  ad_group_ad.ad.name,
  ad_group_ad.ad.type,
  ad_group_ad.status,
  ad_group_ad.policy_summary.approval_status,
  ad_group.id,
  ad_group.name,
  campaign.id,
  campaign.name
FROM ad_group_ad

Azioni di Conversione

SELECT
  conversion_action.id,
  conversion_action.name,
  conversion_action.status,
  conversion_action.type,
  conversion_action.category,
  conversion_action.include_in_conversions_metric,
  conversion_action.counting_type
FROM conversion_action

Pattern di Esecuzione Python

def gaql_query(client, customer_id, query):
    gs = client.get_service('GoogleAdsService')
    results = []
    for batch in gs.search_stream(customer_id=customer_id, query=query):
        for row in batch.results:
            results.append(row)
    return results

Passo 6: Operazioni di Scrittura con `validate_only`

L'API v22 richiede oggetti request, non argomenti keyword. Ogni servizio di mutation (CampaignService, AdGroupService, AdGroupCriterionService, CampaignBudgetService) segue lo stesso pattern:

from google.protobuf import field_mask_pb2

req = client.get_type('MutateCampaignsRequest')
req.customer_id = '1305475941'
req.validate_only = True
op = client.get_type('CampaignOperation')
op.create.name = 'Nome Campagna'
op.create.status = client.enums.CampaignStatusEnum.PAUSED
op.create.advertising_channel_type = client.enums.AdvertisingChannelTypeEnum.SEARCH
op.create.campaign_budget = 'customers/1305475941/campaignBudgets/123456789'
op.create.manual_cpc.enhanced_cpc_enabled = False
req.operations.append(op)  # .append(), non .add()
client.get_service('CampaignService').mutate_campaigns(request=req)

Differenze chiave rispetto alle versioni precedenti dell'API:

  • validate_only è un campo sull'oggetto request, non un argomento keyword del metodo mutate
  • Le operazioni vengono aggiunte tramite req.operations.append(op), non req.operations.add(op)
  • I percorsi delle risorse sono stringhe esplicite ('customers/1305475941/campaigns/22479990461'), non valori restituiti da metodi helper

Gate di Mutation a Tre Fasi

Per la sicurezza in produzione, le mutation passano attraverso tre gate:

Gate 1 — validate_only=True: L'API convalida la struttura della richiesta, i campi obbligatori e i riferimenti alle risorse. Nessun oggetto viene creato. Esegui questo per primo per ogni mutation.

Gate 2 — Creazione in Pausa: Imposta validate_only=False e crea l'oggetto con status=PAUSED. Verifica il risultato con una query di lettura prima di procedere.

Gate 3 — Attivazione Live: Una chiamata API separata che aggiorna solo il campo status a ENABLED. Questa dovrebbe richiedere un'approvazione esplicita, non essere incorporata nella creazione.

# Gate 3: attivare una campagna precedentemente creata in pausa
req = client.get_type('MutateCampaignsRequest')
req.customer_id = '1305475941'
req.validate_only = False
op = client.get_type('CampaignOperation')
op.update.resource_name = 'customers/1305475941/campaigns/22479990461'
op.update.status = client.enums.CampaignStatusEnum.ENABLED
op.update_mask.CopyFrom(field_mask_pb2.FieldMask(paths=['status']))
req.operations.append(op)
client.get_service('CampaignService').mutate_campaigns(request=req)

Aggiornamento Budget

req = client.get_type('MutateCampaignBudgetsRequest')
req.customer_id = '1305475941'
req.validate_only = True
op = client.get_type('CampaignBudgetOperation')
op.update.resource_name = 'customers/1305475941/campaignBudgets/123456789'
op.update.amount_micros = 1_000_000  # $1.00 al giorno
op.update_mask.CopyFrom(field_mask_pb2.FieldMask(paths=['amount_micros']))
req.operations.append(op)
client.get_service('CampaignBudgetService').mutate_campaign_budgets(request=req)

Aggiornamento Offerta Gruppo di Annunci

req = client.get_type('MutateAdGroupsRequest')
req.customer_id = '1305475941'
req.validate_only = True
op = client.get_type('AdGroupOperation')
op.update.resource_name = 'customers/1305475941/adGroups/165827073530'
op.update.cpc_bid_micros = 250_000  # $0.25
op.update_mask.CopyFrom(field_mask_pb2.FieldMask(paths=['cpc_bid_micros']))
req.operations.append(op)
client.get_service('AdGroupService').mutate_ad_groups(request=req)

Creazione Parola Chiave

req = client.get_type('MutateAdGroupCriteriaRequest')
req.customer_id = '1305475941'
req.validate_only = True
op = client.get_type('AdGroupCriterionOperation')
op.create.ad_group = 'customers/1305475941/adGroups/165827073530'
op.create.status = client.enums.AdGroupCriterionStatusEnum.PAUSED
op.create.keyword.text = 'parola chiave esempio'
op.create.keyword.match_type = client.enums.KeywordMatchTypeEnum.EXACT
req.operations.append(op)
client.get_service('AdGroupCriterionService').mutate_ad_group_criteria(request=req)

Gestione degli Errori

from google.ads.googleads.errors import GoogleAdsException

try:
    response = service.mutate_campaigns(request=req)
except GoogleAdsException as ex:
    for error in ex.failure.errors:
        print(f'{error.error_code}: {error.message}')

Codici di errore comuni e loro significato:

Codice ErroreCausa
`RESOURCE_NOT_FOUND`Token di Accesso di Test contro un account di produzione (previsto) oppure risorsa effettivamente mancante
`REQUIRED`Campo obbligatorio mancante (es. strategia di offerta nella creazione della campagna)
`UNRECOGNIZED_FIELD`Campo di una versione API diversa o nome campo errato
`INVALID_ARGUMENT`Il valore del campo non supera la validazione (es. budget negativo)
`PERMISSION_DENIED`L'utente OAuth non ha accesso all'account target

Livelli di Accesso API

L'accesso alla Google Ads API è a livelli, non binario:

LivelloProvisioningLettura (Produzione)Scrittura (Produzione)Scrittura (Account di Test)Limite Operazioni Giornaliere
**Accesso di Test**Immediato da API Center MCC15.000
**Accesso Base**Richiesta + revisione Compliance15.000
**Accesso Standard**Soglia di spesa + revisioneIllimitato

L'Accesso di Test è sufficiente per lo sviluppo: autenticazione, query GAQL, test validate_only e lettura/scrittura completa su account di test Google Ads. Il passaggio all'Accesso Base è condizionato dalla revisione di compliance, non dalla spesa.

L'Accesso Standard rimuove il limite giornaliero di operazioni. Richiede uno storico di spesa gestita tramite il token oppure una richiesta separata.

Account di Test

Gli account di test sono account Google Ads gratuiti senza fatturazione. Accettano qualsiasi mutation. Per crearne uno: nell'MCC, vai a Account → + → Crea nuovo account → Account di test. Collegalo all'MCC e usalo come customer_id target durante lo sviluppo con Accesso di Test.

`validate_only` tra i Livelli di Accesso

validate_only=True funziona a tutti i livelli di accesso, ma l'Accesso di Test rifiuta comunque le mutation validate_only su account di produzione con RESOURCE_NOT_FOUND. Non è una limitazione di validate_only — è lo stesso controllo di accesso applicato a tutte le operazioni di scrittura indipendentemente dal flag validate_only.

Conversion Tracking: Lettura API vs Stato Funzionale

L'interfaccia Google Ads potrebbe mostrare "Configura il monitoraggio delle conversioni" come raccomandazione anche quando un'azione di conversione esiste ed è abilitata. L'API può confermare l'esistenza di un'azione di conversione (tipo WEBPAGE, categoria PURCHASE, stato ENABLED, include_in_conversions_metric: true), ma questo dimostra solo che l'oggetto azione esiste. Non dimostra che il sito web emetta l'evento AW-.../label corrispondente, che il tag Google sia caricato o che un segnale di conversione sia stato ricevuto.

Durante l'audit del conversion tracking, separa il controllo API (metadati dell'azione di conversione) dal controllo browser (scheda network, caricamento tag, attivazione eventi). L'API è la fonte di verità per la configurazione; il browser è la fonte di verità per l'esecuzione.

Il pattern per l'attribuzione dei ricavi lato backend si applica qui: se gli eventi di conversione lato client non sono affidabili, pianifica il caricamento delle conversioni lato server o offline tramite il ConversionUploadService della Google Ads API come fallback.

Versionamento API

La libreria Python google-ads viene distribuita con più versioni API. A giugno 2026, sono disponibili dalla v19 alla v22. Ogni versione aggiunge, depreca o rimuove campi GAQL e metodi di servizio.

Regole per la selezione della versione:

  • Specifica la versione esplicitamente in load_from_storage(version='v22'). Il default potrebbe essere obsoleto.
  • Controlla le note di rilascio della Google Ads API per le deprecazioni dei campi prima di aggiornare.
  • Il metodo search_stream è disponibile dalla v6+ ed è il percorso di lettura raccomandato.
  • Protobuf-plus (use_proto_plus: true) è richiesto per la v12+.
  • Il pattern a oggetti request per le mutation (mutate_campaigns(request=req)) ha sostituito lo stile a keyword argument nella v17+.

Sicurezza Locale

Tutti i file di credenziali devono rimanere fuori dal controllo versione:

~/.hermes/
  google-ads.yaml          # 0600 — dev token, client OAuth, refresh token
  google_ads_token.json    # 0600 — token OAuth, scope adwords
  google_client_secret.json # 0600 — client ID + secret OAuth

Per il repository di lavoro, aggiungi un .gitignore:

google-ads.yaml
*_token*.json
*_secret*.json

Un file template con valori placeholder può essere committato per documentare la struttura prevista:

# google-ads.yaml.template — committa questo, compila localmente
developer_token: INSERISCI_DEV_TOKEN
client_id: INSERISCI_CLIENT_ID
client_secret: INSERISCI_CLIENT_SECRET
refresh_token: INSERISCI_REFRESH_TOKEN
login_customer_id: INSERISCI_MCC_ID
use_proto_plus: true

Campi Obbligatori per le Operazioni Comuni

OperazioneCampi Obbligatori
Crea Campagna (Search)`name`, `status`, `advertising_channel_type`, `campaign_budget`, `manual_cpc` o strategia di offerta
Crea Gruppo di Annunci (Search)`name`, `status`, `type_`, `cpc_bid_micros`, `campaign`
Crea Parola Chiave`ad_group`, `status`, `keyword.text`, `keyword.match_type`
Aggiorna Stato Campagna`resource_name`, `status`, `update_mask` (paths: `['status']`)
Aggiorna Budget`resource_name`, `amount_micros`, `update_mask` (paths: `['amount_micros']`)

Un campo obbligatorio mancante restituisce REQUIRED con il nome del campo. Un update_mask mancante in un'operazione di aggiornamento viene ignorato silenziosamente — il campo non viene aggiornato e non viene restituito alcun errore.

Script di Test Completo

Un singolo script che legge tutti i tipi di entità ed esegue test di scrittura validate_only:

# google_ads_api_test.py — legge tutte le entità + test di scrittura validate_only
import warnings
warnings.filterwarnings('ignore')
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
from google.protobuf import field_mask_pb2

CFG = '/path/to/google-ads.yaml'
CID = '1305475941'
c = GoogleAdsClient.load_from_storage(CFG, version='v22')
gs = c.get_service('GoogleAdsService')

def gaql(query, label):
    print(f'\n=== {label} ===')
    n = 0
    for batch in gs.search_stream(customer_id=CID, query=query):
        for row in batch.results:
            n += 1
    print(f'{n} righe')

# Legge tutte le entità
gaql("SELECT customer.id, customer.descriptive_name FROM customer WHERE customer.id = ...", "CUSTOMER")
gaql("SELECT campaign.id, campaign.name, campaign.status, ...  FROM campaign", "CAMPAGNE")
gaql("SELECT campaign_budget.id, campaign_budget.name, ...  FROM campaign_budget", "BUDGET")
gaql("SELECT ad_group.id, ad_group.name, ...  FROM ad_group", "GRUPPI DI ANNUNCI")
gaql("SELECT ad_group_criterion.keyword.text, ...  FROM keyword_view WHERE ...", "PAROLE CHIAVE")
gaql("SELECT ad_group_ad.ad.id, ...  FROM ad_group_ad", "ANNUNCI")
gaql("SELECT conversion_action.id, ...  FROM conversion_action", "CONVERSIONI")

# Test di scrittura
def test_write(name, fn):
    print(f'\n--- {name} ---')
    try:
        fn()
        print('PASS')
    except GoogleAdsException as e:
        for err in e.failure.errors:
            print(f'{err.error_code}: {err.message}')

def test_create_campaign():
    req = c.get_type('MutateCampaignsRequest')
    req.customer_id = CID
    req.validate_only = True
    op = c.get_type('CampaignOperation')
    op.create.name = 'TEST_CAMPAGNA'
    op.create.status = c.enums.CampaignStatusEnum.PAUSED
    op.create.advertising_channel_type = c.enums.AdvertisingChannelTypeEnum.SEARCH
    op.create.campaign_budget = f'customers/{CID}/campaignBudgets/123456789'
    op.create.manual_cpc.enhanced_cpc_enabled = False
    req.operations.append(op)
    c.get_service('CampaignService').mutate_campaigns(request=req)

test_write('Crea Campagna (validate_only)', test_create_campaign)
print('\nFatto.')

Versione eseguibile completa disponibile nella skill per le operazioni Google Ads API nel repository delle operazioni pubblicitarie.

Riferimenti

Articoli correlati