tva
← Insights

Google Ads API: Configuração, Autenticação, Operações de Leitura e Escrita

Um guia técnico para configurar o acesso programático ao Google Ads — da arquitetura à primeira chamada de API. Abrange configuração de MCC, OAuth 2.0, níveis de Developer Token, a biblioteca Python google-ads, consultas de leitura em GAQL e o padrão de escrita validate_only.

Arquitetura

São necessárias três entidades separadas antes que uma única chamada de API funcione:

EntidadeFinalidadeExemplo
**Conta de Gestor (MCC)**Emite o Developer Token`login_customer_id`
**Projeto Google Cloud**Cliente OAuth 2.0 para autenticaçãoClient ID + Secret
**Conta Google Ads**Alvo de todas as operações da API`customer_id`

Não são a mesma conta. Uma MCC pode possuir um Developer Token sem ser proprietária da conta de destino — apenas precisa estar vinculada como gestora. O Projeto Google Cloud pode estar sob uma identidade Google diferente da MCC. A conta de destino é a conta de anunciante onde as campanhas residem.

Esta separação é importante porque o API Center (onde os Developer Tokens são provisionados) só está disponível dentro de Contas de Gestor. Uma conta Google Ads autónoma não pode aceder ao API Center, mesmo que tenha utilizadores administrativos.

O cabeçalho da chamada de API reúne estas três identidades mais um token OAuth Bearer:

developer-token: <do API Center da MCC>
login-customer-id: <ID numérico da MCC, sem hífenes>
customer-id: <ID numérico da conta de destino>
Authorization: Bearer <token de acesso OAuth 2.0>

Pré-requisitos

  • Uma Conta de Gestor Google Ads com configuração concluída
  • A conta de anúncios de destino vinculada à Conta de Gestor e o vínculo aceite
  • Um Projeto Google Cloud com a API Google Ads ativada
  • Python 3.9+ com a biblioteca google-ads (pip install google-ads)
  • Um navegador para o fluxo único de consentimento OAuth

Passo 1: Conta de Gestor e Developer Token

O API Center encontra-se em Ferramentas e Definições → Configuração → API Center dentro de uma Conta de Gestor. Se o API Center estiver ausente, a conta ou não é uma Conta de Gestor ou a sua configuração está incompleta.

A partir do API Center, solicite um Developer Token. Defina o tipo de conta como Advertiser (não Agency ou Third-Party) quando o token se destina às suas próprias contas. O token é emitido imediatamente com o nível Test Access.

O Test Access permite:

  • Operações de leitura em qualquer conta vinculada
  • Operações de escrita apenas em contas de teste
  • Nenhuma operação de escrita em contas de produção
  • O erro para escritas em contas de produção com Test Access é RESOURCE_NOT_FOUND — e não PERMISSION_DENIED. Isto é confuso porque o recurso existe e é legível. O erro significa que a API se recusa a encaminhar a mutação sob o nível de acesso atual.

    Candidatura a Basic Access

    Para escrever em contas de produção, candidate-se a Basic Access a partir da mesma página do API Center. O formulário de candidatura coloca 12 perguntas. Para uso interno nas suas próprias contas, as respostas relevantes são:

    PerguntaResposta
    Q4: Relação com um Representante GoogleNo
    Q6: Modelo de NegócioDescreva o seu caso de uso (e-commerce, geração de leads, reporting interno)
    Q8: Quem terá acessoUtilizadores internos — apenas colaboradores
    Q9: Ferramenta desenvolvida por terceirosNo (scripts/agentes próprios)
    Q10: API de Acompanhamento de Conversões e RemarketingNo (a menos que realmente a utilize)
    Q11: Tipos de CampanhaSearch, Performance Max, Shopping (conforme aplicável)
    Q12: CapacidadesCriação de Campanhas, Gestão de Campanhas, Reporting

    É necessário um documento de design (PDF) para a Q7. Deve descrever a arquitetura da API, o fluxo de autenticação, a estratégia de limitação de taxa, o tratamento de erros e o caso de uso exclusivamente interno. Seja factual — a equipa de compliance revê a consistência com o caso de uso declarado.

    Tempo de processamento em junho de 2026: aproximadamente 3 dias úteis, com uma nota sobre volume de candidaturas superior ao habitual.

    Passo 2: OAuth 2.0

    Configuração do Projeto Google Cloud

    Na Google Cloud Console, sob o projeto relevante:

    1. Ative a Google Ads API (APIs e Serviços → Biblioteca)
    2. Configure o ecrã de consentimento OAuth:
  • Tipo de Utilizador: Internal (apenas utilizadores do seu Google Workspace podem autorizar — dispensa a verificação externa da app)
  • Nome da app, email de suporte, email de contacto do programador
  • Adicione o scope em Acesso a Dados: https://www.googleapis.com/auth/adwords
  • Crie um OAuth Client ID:
  • Tipo de aplicação: Desktop app
  • Descarregue o JSON do segredo de cliente
  • O tipo Desktop app utiliza http://localhost como URI de redirecionamento. Isto está correto — o fluxo OAuth abre um navegador, o Google redireciona para localhost com o código de autorização e um servidor HTTP local captura-o.

    Geração do Refresh Token

    Um script Python que executa um servidor HTTP local em 127.0.0.1:0 (porta aleatória), imprime o URL de autorização, aguarda um pedido, extrai o parâmetro code e troca-o por tokens:

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

    Dois parâmetros são importantes:

  • access_type='offline' — devolve um refresh token, não apenas um access token
  • prompt='consent' — força um novo ecrã de consentimento mesmo que o utilizador tenha autorizado anteriormente; necessário para obter um novo refresh token se os scopes tiverem mudado
  • O refresh token é permanente a menos que seja revogado, o utilizador altere a palavra-passe ou o token fique sem uso durante 6 meses.

    Armadilha de Scope com Clientes Existentes

    Se o cliente OAuth foi previamente autorizado para outros scopes (Gmail, Drive, Calendar), o Google devolve a união dos scopes antigos e novos. A biblioteca google-auth-oauthlib rejeita esta discrepância de scope por padrão. Defina a variável de ambiente antes de carregar o fluxo:

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

    Em alternativa, omita include_granted_scopes=true do URL de autorização, o que indica ao Google para solicitar apenas o scope explicitamente indicado.

    Passo 3: `google-ads.yaml`

    A biblioteca lê a configuração de um ficheiro YAML:

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

    Regras de segurança:

  • Permissões do ficheiro: 0600 (apenas leitura/escrita pelo proprietário)
  • Nunca submeter ao controlo de versões
  • Armazenar fora de qualquer repositório Git
  • Para configurações de equipa, distribuir via gestor de segredos, não via ficheiro partilhado
  • login_customer_id é o ID numérico da MCC sem hífenes. Indica à API qual o Developer Token da conta de gestor a utilizar. O customer_id de destino é passado no momento da chamada, não na configuração.

    use_proto_plus: true ativa a interface protobuf-plus, que é necessária para as versões atuais da API.

    Passo 4: Inicialização do Cliente 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')

    O parâmetro version seleciona explicitamente a versão da API. A biblioteca google-ads é distribuída com várias versões instaladas em paralelo (v19 a v22 em junho de 2026). Especifique sempre a versão — a padrão pode estar desatualizada ou não suportar campos mais recentes.

    Verifique a conectividade:

    cs = client.get_service('CustomerService')
    customers = cs.list_accessible_customers()
    ids = [r.split('/')[-1] for r in customers.resource_names]
    # Exemplo de saída: ['1305475941', '3977581086', '1911599764']

    Passo 5: Operações de Leitura (GAQL)

    O Google Ads utiliza GAQL (Google Ads Query Language), uma sintaxe semelhante a SQL. Todas as operações de leitura passam por GoogleAdsService.search_stream(), que evita paginação e é preferível a search() para a maioria das consultas.

    Campanhas com Métricas de 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

    Orçamentos de Campanha

    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

    Grupos de Anúncios

    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

    Palavras-chave (por Impressões)

    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

    Anúncios com Estado de Política

    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

    Ações de Conversão

    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

    Padrão de Execução 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: Operações de Escrita com `validate_only`

    A API v22 exige objetos de pedido, não argumentos nomeados. Todos os serviços de mutação (CampaignService, AdGroupService, AdGroupCriterionService, CampaignBudgetService) seguem o mesmo padrão:

    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 = 'Campaign Name'
    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(), não .add()
    client.get_service('CampaignService').mutate_campaigns(request=req)

    Diferenças-chave em relação a versões anteriores da API:

  • validate_only é um campo no objeto de pedido, não um argumento nomeado do método mutate
  • As operações são anexadas via req.operations.append(op), e não req.operations.add(op)
  • Os caminhos de recursos são strings explícitas ('customers/1305475941/campaigns/22479990461'), não valores devolvidos por métodos auxiliares
  • Portão de Mutação em Três Fases

    Para segurança em produção, as mutações passam por três portões:

    Portão 1 — validate_only=True: A API valida a estrutura do pedido, campos obrigatórios e referências de recursos. Nenhum objeto é criado. Execute isto primeiro para cada mutação.

    Portão 2 — Criação em Pausa: Defina validate_only=False e crie o objeto com status=PAUSED. Verifique o resultado com uma consulta de leitura antes de prosseguir.

    Portão 3 — Ativação Real: Uma chamada de API separada que atualiza apenas o campo status para ENABLED. Isto deve exigir aprovação explícita, não ser incorporado na criação.

    # Portão 3: ativar uma campanha previamente criada em 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)

    Atualização de Orçamento

    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 por dia
    op.update_mask.CopyFrom(field_mask_pb2.FieldMask(paths=['amount_micros']))
    req.operations.append(op)
    client.get_service('CampaignBudgetService').mutate_campaign_budgets(request=req)

    Atualização de Lance de Grupo de Anúncios

    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)

    Criação de Palavra-chave

    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 = 'example keyword'
    op.create.keyword.match_type = client.enums.KeywordMatchTypeEnum.EXACT
    req.operations.append(op)
    client.get_service('AdGroupCriterionService').mutate_ad_group_criteria(request=req)

    Tratamento de Erros

    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}')

    Códigos de erro comuns e o seu significado:

    Código de ErroCausa
    `RESOURCE_NOT_FOUND`Token Test Access contra uma conta de produção (esperado) ou recurso genuinamente inexistente
    `REQUIRED`Campo obrigatório em falta (ex.: estratégia de lance na criação de campanha)
    `UNRECOGNIZED_FIELD`Campo de uma versão diferente da API ou nome de campo incorreto
    `INVALID_ARGUMENT`Valor de campo falha validação (ex.: orçamento negativo)
    `PERMISSION_DENIED`Utilizador OAuth não tem acesso à conta de destino

    Níveis de Acesso à API

    O acesso à API Google Ads é escalonado, não binário:

    NívelProvisionamentoLeitura (Produção)Escrita (Produção)Escrita (Contas de Teste)Limite Diário de Operações
    **Test Access**Imediato do API Center da MCC15.000
    **Basic Access**Candidatura + Revisão de compliance15.000
    **Standard Access**Limiar de investimento + revisãoIlimitado

    O Test Access é suficiente para desenvolvimento: autenticação, consultas GAQL, testes validate_only e leitura/escrita completas em contas de teste do Google Ads. A passagem para Basic Access é condicionada por revisão de compliance, não por investimento.

    O Standard Access remove o limite diário de operações. Requer um histórico de investimento gerido através do token ou uma candidatura separada.

    Contas de Teste

    Contas de teste são contas Google Ads gratuitas, sem faturação. Aceitam qualquer mutação. Para criar uma: na MCC, vá a Contas → + → Criar nova conta → Conta de teste. Vincule-a à MCC e utilize-a como customer_id de destino durante o desenvolvimento com Test Access.

    `validate_only` Entre Níveis de Acesso

    validate_only=True funciona em todos os níveis de acesso, mas o Test Access continua a rejeitar mutações validate_only em contas de produção com RESOURCE_NOT_FOUND. Isto não é uma limitação do validate_only — é o mesmo controlo de acesso aplicado a todas as operações de escrita, independentemente da flag validate_only.

    Acompanhamento de Conversões: Leitura da API vs. Estado Funcional

    A interface do Google Ads pode mostrar "Configurar acompanhamento de conversões" como recomendação mesmo quando existe uma ação de conversão ativa. A API pode confirmar a existência de uma ação de conversão (tipo WEBPAGE, categoria PURCHASE, estado ENABLED, include_in_conversions_metric: true), mas isto apenas prova que o objeto da ação existe. Não prova que o website emite o evento AW-.../label correspondente, que a Google tag está carregada ou que um sinal de conversão foi recebido.

    Ao auditar o acompanhamento de conversões, separe a verificação da API (metadados da ação de conversão) da verificação no navegador (separador de rede, carregamento de tags, disparo de eventos). A API é a fonte de verdade para a configuração; o navegador é a fonte de verdade para a execução.

    O padrão de atribuição de receita no backend aplica-se aqui: se os eventos de conversão do lado do cliente não forem fiáveis, planeie o upload de conversões via servidor ou offline através do ConversionUploadService da Google Ads API como alternativa.

    Versionamento da API

    A biblioteca Python google-ads é distribuída com várias versões da API. Em junho de 2026, estão disponíveis as versões v19 a v22. Cada versão adiciona, descontinua ou remove campos GAQL e métodos de serviço.

    Regras para seleção de versão:

  • Especifique a versão explicitamente em load_from_storage(version='v22'). A padrão pode estar desatualizada.
  • Consulte as notas de versão da Google Ads API para despromoções de campos antes de atualizar.
  • O método search_stream está disponível a partir da v6+ e é o caminho de leitura recomendado.
  • Protobuf-plus (use_proto_plus: true) é obrigatório para v12+.
  • O padrão de objeto de pedido para mutações (mutate_campaigns(request=req)) substituiu o estilo de argumentos nomeados na v17+.
  • Segurança Local

    Todos os ficheiros de credenciais devem permanecer fora do controlo de versões:

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

    Para o repositório de trabalho, adicione um .gitignore:

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

    Um ficheiro de modelo com valores placeholder pode ser submetido para documentar a estrutura esperada:

    # google-ads.yaml.template — submeter este, preencher localmente
    developer_token: INSERT_DEV_TOKEN
    client_id: INSERT_CLIENT_ID
    client_secret: INSERT_CLIENT_SECRET
    refresh_token: INSERT_REFRESH_TOKEN
    login_customer_id: INSERT_MCC_ID
    use_proto_plus: true

    Campos Obrigatórios para Operações Comuns

    OperaçãoCampos Obrigatórios
    Criar Campanha (Search)`name`, `status`, `advertising_channel_type`, `campaign_budget`, `manual_cpc` ou estratégia de lance
    Criar Grupo de Anúncios (Search)`name`, `status`, `type_`, `cpc_bid_micros`, `campaign`
    Criar Palavra-chave`ad_group`, `status`, `keyword.text`, `keyword.match_type`
    Atualizar Estado da Campanha`resource_name`, `status`, `update_mask` (paths: `['status']`)
    Atualizar Orçamento`resource_name`, `amount_micros`, `update_mask` (paths: `['amount_micros']`)

    A falta de um campo obrigatório devolve REQUIRED com o nome do campo. A ausência de update_mask numa operação de atualização é silenciosamente ignorada — o campo não é atualizado e nenhum erro é devolvido.

    Script de Teste Completo

    Um único script que lê todos os tipos de entidade e executa testes de escrita validate_only:

    # google_ads_api_test.py — ler todas as entidades + testes de escrita 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} rows')
    
    # Ler todas as entidades
    gaql("SELECT customer.id, customer.descriptive_name FROM customer WHERE customer.id = ...", "CUSTOMER")
    gaql("SELECT campaign.id, campaign.name, campaign.status, ...  FROM campaign", "CAMPAIGNS")
    gaql("SELECT campaign_budget.id, campaign_budget.name, ...  FROM campaign_budget", "BUDGETS")
    gaql("SELECT ad_group.id, ad_group.name, ...  FROM ad_group", "AD GROUPS")
    gaql("SELECT ad_group_criterion.keyword.text, ...  FROM keyword_view WHERE ...", "KEYWORDS")
    gaql("SELECT ad_group_ad.ad.id, ...  FROM ad_group_ad", "ADS")
    gaql("SELECT conversion_action.id, ...  FROM conversion_action", "CONVERSIONS")
    
    # Testes de escrita
    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_CAMPAIGN'
        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('Create Campaign (validate_only)', test_create_campaign)
    print('\nDone.')

    Versão executável completa no skill de operações da Google Ads API no repositório de operações publicitárias.

    Referências

  • Documentação da Google Ads API
  • Biblioteca Python google-ads
  • Referência GAQL
  • OAuth 2.0 para Aplicações Desktop
  • Construir um Assistente de IA Específico de Projeto via Telegram — mesmo padrão OAuth aplicado a um scope diferente do Google
  • Arquitetura de Referência para uma Camada de Operações de IA Baseada em Threads — o modelo operacional onde a automação da API se integra
  • Operações Individuais em Escala: Gerir Dezenas de Projetos com uma Equipa Pequena — por que razão a automação da API substitui o trabalho manual na UI em escala
  • Artigos relacionados