Campaign Management Samples

The code samples below provide examples for managing campaigns using the AdWords API. Client Library.

Add a campaign group and sets a performance target for that group

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2017, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This code example adds a campaign group and sets a performance target for that
# group. To get campaigns, run get_campaigns.rb. To download reports, run
# download_criteria_report_with_awql.rb.

require 'date'

require 'adwords_api'

def add_campaign_groups_and_performance_targets(campaign_ids)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  campaign_group = create_campaign_group(adwords)
  add_campaigns_to_group(adwords, campaign_group, campaign_ids)
  create_performance_target(adwords, campaign_group)
end

def create_campaign_group(adwords)
  campaign_group_srv = adwords.service(:CampaignGroupService, API_VERSION)

  campaign_group = {
    :name => "Mars campaign group #%d" % (Time.new.to_f * 1000).to_i
  }

  operation = {
    :operand => campaign_group,
    :operator => 'ADD'
  }

  new_campaign_group = campaign_group_srv.mutate([operation])[:value].first
  puts "Campaign group with ID %d and name '%s' was created." % [
    new_campaign_group[:id],
    new_campaign_group[:name]
  ]

  return new_campaign_group
end

def add_campaigns_to_group(adwords, campaign_group, campaign_ids)
  campaign_srv = adwords.service(:CampaignService, API_VERSION)

  operations = campaign_ids.map do |campaign_id|
    {
      :operand => {
        :id => campaign_id,
        :campaign_group_id => campaign_group[:id]
      },
      :operator => 'SET'
    }
  end

  return_value = campaign_srv.mutate(operations)
  puts ("The following campaign IDs were added to the campaign group with " +
      " ID %d:") % campaign_group[:id]
  return_value[:value].each do |campaign|
    puts "\t%d" % campaign[:id]
  end
end

def create_performance_target(adwords, campaign_group)
  campaign_group_performance_target_srv = adwords.service(
      :CampaignGroupPerformanceTargetService, API_VERSION)

  campaign_group_performance_target = {
    :campaign_group_id => campaign_group[:id],
    :performance_target => {
      # Keep the CPC for the campaigns <=$3.
      :efficiency_target_type => 'CPC_LESS_THAN_OR_EQUAL_TO',
      :efficiency_target_value => 3_000_000,
      # Keep the maximum spend under $50.
      :spend_target_type => 'MAXIMUM',
      :spend_target => {
        :micro_amount => 50_000_000
      },
      # Aim for at least 3000 clicks.
      :volume_target_value => 3000,
      :volume_goal_type => 'MAXIMIZE_CLICKS',
      # Start the performance target today, and run it for the next 90 days.
      :start_date => DateTime.parse((Date.today).to_s).strftime('%Y%m%d'),
      :end_date => DateTime.parse((Date.today + 90).to_s).strftime('%Y%m%d')
    }
  }

  operation = {
    :operand => campaign_group_performance_target,
    :operator => 'ADD'
  }

  new_campaign_group_performance_target =
      campaign_group_performance_target_srv.mutate([operation])[:value].first

  puts ("Campaign group performance target with ID %s was added for campaign " +
      "group ID %d") % [new_campaign_group_performance_target[:id],
      new_campaign_group_performance_target[:campaign_group_id]]
end

if __FILE__ == $0
  API_VERSION = :v201705

  begin
    campaign_id_1 = 'INSERT_CAMPAIGN_ID_1_HERE'.to_i
    campaign_id_2 = 'INSERT_CAMPAIGN_ID_2_HERE'.to_i
    add_campaign_groups_and_performance_targets([campaign_id_1, campaign_id_2])

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Add a label to multiple campaigns

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2014, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example adds a label to multiple campaigns.

require 'adwords_api'

def add_campaign_labels(campaign_ids, label_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  campaign_srv = adwords.service(:CampaignService, API_VERSION)

  operations = campaign_ids.map do |campaign_id|
    {
      :operator => 'ADD',
      :operand => {:campaign_id => campaign_id, :label_id => label_id}
    }
  end

  response = campaign_srv.mutate_label(operations)
  if response and response[:value]
    response[:value].each do |campaign_label|
      puts "Campaign label for campaign ID %d and label ID %d was added.\n" %
          [campaign_label[:campaign_id], campaign_label[:label_id]]
    end
  end
end

if __FILE__ == $0
  API_VERSION = :v201705

  begin
    campaign_id_1 = 'INSERT_CAMPAIGN_ID_1_HERE'.to_i
    campaign_id_2 = 'INSERT_CAMPAIGN_ID_2_HERE'.to_i
    label_id = 'INSERT_LABEL_ID_HERE'.to_i
    add_campaign_labels([campaign_id_1, campaign_id_2], label_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Add complete campaigns using batch jobs

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2015, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This code sample illustrates how to use BatchJobService to create a complete
# campaign, including ad groups and keywords.

require 'adwords_api'

def add_complete_campaigns_using_batch_job()
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  batch_job_srv = adwords.service(:BatchJobService, API_VERSION)
  batch_job_utils = adwords.batch_job_utils(API_VERSION)

  # Create a BatchJob.
  add_op = {
    :operator => 'ADD',
    :operand => {}
  }

  response = batch_job_srv.mutate([add_op])
  batch_job = response[:value].first

  # Get the upload URL from the new job.
  upload_url = batch_job[:upload_url][:url]
  puts "Created BatchJob with ID %d, status '%s', and upload URL %s." %
      [batch_job[:id], batch_job[:status], upload_url]

  # Create a temporary ID generator that will produce a sequence of descending
  # negative numbers.
  temp_id_generator = TempIdGenerator.new()

  # Create an array of hashed operations generated from the batch_job_utils.
  operations = []

  # Create an operation to create a new budget.
  budget_operation = build_budget_operation(temp_id_generator)
  operations << budget_operation

  # Create operations to create new campaigns.
  campaign_operations = build_campaign_operations(
      temp_id_generator, budget_operation)
  operations += campaign_operations

  # Create operations to create new negative keyword criteria for each
  # campaign.
  operations += build_campaign_criterion_operations(campaign_operations)

  # Create operations to create new ad groups.
  ad_group_operations = build_ad_group_operations(
      temp_id_generator, campaign_operations)
  operations += ad_group_operations

  # Create operations to create new ad group criteria (keywords).
  operations += build_ad_group_criterion_operations(ad_group_operations)

  # Create operations to create new ad group ads (text ads).
  operations += build_ad_group_ad_operations(ad_group_operations)

  # Use the batch_job_utils to upload all operations.
  batch_job_utils.upload_operations(operations, upload_url)
  puts "Uploaded %d operations for batch job with ID %d." %
      [operations.size, batch_job[:id]]

  # Poll for completion of the batch job using an exponential back off.
  poll_attempts = 0
  is_pending = true
  selector = {
    :fields => ['Id', 'Status', 'DownloadUrl', 'ProcessingErrors',
        'ProgressStats'],
    :predicates => [{
      :field => 'Id',
      :operator => 'IN',
      :values => [batch_job[:id]]
    }]
  }

  begin
    sleep_seconds = 30 * (2 ** poll_attempts)
    puts "Sleeping for %d seconds" % sleep_seconds
    sleep(sleep_seconds)

    batch_job = batch_job_srv.get(selector)[:entries].first

    puts "Batch job ID %d has status '%s'." %
        [batch_job[:id], batch_job[:status]]

    poll_attempts += 1
    is_pending = PENDING_STATUSES.include?(batch_job[:status])
  end while is_pending and poll_attempts < MAX_POLL_ATTEMPTS

  if is_pending
    raise StandardError,
        "Job is still in pending state after polling %d times." %
        MAX_POLL_ATTEMPTS
  end

  unless batch_job[:processing_errors].nil?
    batch_job[:processing_errors].each_with_index do |processing_error, i|
      puts ("Processing error [%d]: errorType=%s, trigger=%s, errorString=%s" +
          "fieldPath=%s, reason=%s") % [i, processing_error[:api_error_type],
          processing_error[:trigger], processing_error[:error_string],
          processing_error[:field_path], processing_error[:reason]]
    end
  end

  unless batch_job[:download_url].nil? or batch_job[:download_url][:url].nil?
    mutate_response = batch_job_utils.get_job_results(
        batch_job[:download_url][:url])
    puts "Downloaded results from '%s':" % batch_job[:download_url][:url]
    mutate_response.each do |mutate_result|
      outcome = "FAILURE"
      outcome = "SUCCESS" if mutate_result[:error_list].nil?
      puts "  Operation [%d] - %s" % [mutate_result[:index], outcome]
    end
  end
end

# Custom class to generate temporary negative IDs for created entities to
# reference each other.
class TempIdGenerator
  def initialize()
    @counter = -1
  end

  def next()
    ret = @counter
    @counter -= 1
    return ret
  end
end

def get_time_microseconds()
  return (Time.now.to_f * 1000000).to_i
end

def build_budget_operation(temp_id_generator)
  budget = {
    :budget_id => temp_id_generator.next,
    :name => "Interplanetary Cruise %d" % get_time_microseconds(),
    :amount => {
      :micro_amount => 50000000
    },
    :delivery_method => 'STANDARD'
  }
  budget_operation = {
    # The xsi_type of the operation can usually be guessed by the API because
    # a given service only handles one type of operation. However, batch jobs
    # process operations of different types, so the xsi_type must always be
    # explicitly defined for these operations.
    :xsi_type => 'BudgetOperation',
    :operator => 'ADD',
    :operand => budget
  }
  return budget_operation
end

def build_campaign_operations(temp_id_generator, budget_operation)
  budget_id = budget_operation[:operand][:budget_id]

  operations = []
  NUMBER_OF_CAMPAIGNS_TO_ADD.times do
    campaign = {
      :name => "Batch Campaign %s" % get_time_microseconds(),
      # Recommendation: Set the campaign to PAUSED when creating it to stop the
      # ads from immediately serving. Set to ENABLED once you've added
      # targeting and the ads are ready to serve.
      :status => 'PAUSED',
      :id => temp_id_generator.next,
      :advertising_channel_type => 'SEARCH',
      :budget => {
        :budget_id => budget_id
      },
      :bidding_strategy_configuration => {
        :bidding_strategy_type => 'MANUAL_CPC',
        # You can optionally provide a bidding scheme in place of the type.
        :bidding_scheme => {
          :xsi_type => 'ManualCpcBiddingScheme',
          :enhanced_cpc_enabled => false
        }
      }
    }
    operation = {
      :xsi_type => 'CampaignOperation',
      :operator => 'ADD',
      :operand => campaign
    }
    operations << operation
  end

  return operations
end

def build_campaign_criterion_operations(campaign_operations)
  operations = []
  campaign_operations.each do |campaign_operation|
    keyword = {
      :xsi_type => 'Keyword',
      :match_type => 'BROAD',
      :text => 'venus'
    }
    negative_criterion = {
      :xsi_type => 'NegativeCampaignCriterion',
      :campaign_id => campaign_operation[:operand][:id],
      :criterion => keyword
    }
    operation = {
      :xsi_type => 'CampaignCriterionOperation',
      :operator => 'ADD',
      :operand => negative_criterion
    }
    operations << operation
  end

  return operations
end

def build_ad_group_operations(temp_id_generator, campaign_operations)
  operations = []
  campaign_operations.each do |campaign_operation|
    NUMBER_OF_ADGROUPS_TO_ADD.times do
      ad_group = {
        :campaign_id => campaign_operation[:operand][:id],
        :id => temp_id_generator.next,
        :name => "Batch Ad Group %s" % get_time_microseconds(),
        :bidding_strategy_configuration => {
          :bids => [
            {
              :xsi_type => 'CpcBid',
              :bid => {:micro_amount => 10000000}
            }
          ]
        }
      }
      operation = {
        :xsi_type => 'AdGroupOperation',
        :operator => 'ADD',
        :operand => ad_group
      }
      operations << operation
    end
  end

  return operations
end

def build_ad_group_criterion_operations(ad_group_operations)
  operations = []
  ad_group_operations.each do |ad_group_operation|
    NUMBER_OF_KEYWORDS_TO_ADD.times do |i|
      text = "mars%d" % i

      # Make 50% of keywords invalid to demonstrate error handling.
      text = text + "!!!" if i % 2 == 0
      keyword = {
        :xsi_type => 'Keyword',
        :text => text,
        :match_type => 'BROAD'
      }
      biddable_criterion = {
        :xsi_type => 'BiddableAdGroupCriterion',
        :ad_group_id => ad_group_operation[:operand][:id],
        :criterion => keyword
      }
      operation = {
        :xsi_type => 'AdGroupCriterionOperation',
        :operator => 'ADD',
        :operand => biddable_criterion
      }
      operations << operation
    end
  end

  return operations
end

def build_ad_group_ad_operations(ad_group_operations)
  operations = []
  ad_group_operations.each do |ad_group_operation|
    text_ad = {
      :xsi_type => 'ExpandedTextAd',
      :headline_part1 => 'Luxury Cruise to Mars',
      :headling_part2 => 'Visit the Red Planet in style.',
      :description => 'Low-gravity fun for everyone!',
      :final_urls => ['http://www.example.com/1']
    }
    ad_group_ad = {
      :ad_group_id => ad_group_operation[:operand][:id],
      :ad => text_ad
    }
    operation = {
      :xsi_type => 'AdGroupAdOperation',
      :operator => 'ADD',
      :operand => ad_group_ad
    }
    operations << operation
  end

  return operations
end

if __FILE__ == $0
  API_VERSION = :v201705
  NUMBER_OF_CAMPAIGNS_TO_ADD = 2
  NUMBER_OF_ADGROUPS_TO_ADD = 2
  NUMBER_OF_KEYWORDS_TO_ADD = 5
  MAX_POLL_ATTEMPTS = 5
  PENDING_STATUSES = ['ACTIVE', 'AWAITING_FILE', 'CANCELING']

  begin
    add_complete_campaigns_using_batch_job()

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Add keywords using an incremental batch job

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2015, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example illustrates how to perform multiple requests using the
# BatchJobService using incremental uploads.

require 'adwords_api'

def add_keywords_using_incremental_batch_job(ad_group_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  batch_job_srv = adwords.service(:BatchJobService, API_VERSION)
  batch_job_utils = adwords.batch_job_utils(API_VERSION)

  # Create a BatchJob.
  add_op = {
    :operator => 'ADD',
    :operand => {}
  }

  response = batch_job_srv.mutate([add_op])
  batch_job = response[:value].first

  # Get the upload URL from the new job.
  upload_url = batch_job[:upload_url][:url]
  puts "Created BatchJob with ID %d, status '%s', and upload URL %s." %
      [batch_job[:id], batch_job[:status], upload_url]

  # Upload #1
  incremental_helper = batch_job_utils.start_incremental_upload(upload_url)
  operations = create_keyword_operations(ad_group_id)
  incremental_helper.upload(operations)

  # Upload #2
  operations = create_keyword_operations(ad_group_id)
  incremental_helper.upload(operations)

  # Upload #3
  operations = create_keyword_operations(ad_group_id)
  incremental_helper.upload(operations, true)

  # Poll for completion of the batch job using an exponential back off.
  poll_attempts = 0
  is_pending = true
  cancel_requested = false
  selector = {
    :fields =>
        ['Id', 'Status', 'DownloadUrl', 'ProcessingErrors', 'ProgressStats'],
    :predicates => [{
      :field => 'Id',
      :operator => 'IN',
      :values => [batch_job[:id]]
    }]
  }

  begin
    sleep_seconds = 30 * (2 ** poll_attempts)
    puts "Sleeping for %d seconds" % sleep_seconds
    sleep(sleep_seconds)

    batch_job = batch_job_srv.get(selector)[:entries].first

    puts "Batch job ID %d has status '%s'." %
        [batch_job[:id], batch_job[:status]]

    poll_attempts += 1
    is_pending = PENDING_STATUSES.include?(batch_job[:status])

    if is_pending && !cancel_requested && poll_attempts == MAX_POLL_ATTEMPTS
      batch_job[:status] = 'CANCELING'

      set_op = {
        :operator => 'SET',
        :operand => batch_job
      }

      # Only request cancellation once per job.
      cancel_requested = true

      begin
        batch_job = batch_job_srv.mutate([set_op])[:value].first
        puts "Requested cancellation of batch job with ID %d" % batch_job[:id]
      rescue AdwordsApi::Errors::ApiException => e
        if !e.message.nil? && e.message.include?('INVALID_STATE_CHANGE')
          puts ("Attempt to cancel batch job with ID %d was rejected " +
              "because the job already completed or was canceled.") %
              batch_job[:id]
          next
        end
        raise e
      end
    end
  end while is_pending and poll_attempts < MAX_POLL_ATTEMPTS

  if is_pending
    raise StandardError,
        "Job is still in pending state after polling %d times." %
        MAX_POLL_ATTEMPTS
  end

  if batch_job[:status] == 'CANCELED'
    puts "Job was canceled before completion."
    return
  end

  unless batch_job[:processing_errors].nil?
    batch_job[:processing_errors].each_with_index do |processing_error, i|
      puts ("Processing error [%d]: errorType=%s, trigger=%s, errorString=%s" +
          "fieldPath=%s, reason=%s") % [i, processing_error[:api_error_type],
          processing_error[:trigger], processing_error[:error_string],
          processing_error[:field_path], processing_error[:reason]]
    end
  end

  unless batch_job[:download_url].nil? or batch_job[:download_url][:url].nil?
    mutate_response = batch_job_utils.get_job_results(
        batch_job[:download_url][:url])
    puts "Downloaded results from '%s':" % batch_job[:download_url][:url]
    mutate_response.each do |mutate_result|
      outcome = "FAILURE"
      outcome = "SUCCESS" if mutate_result[:error_list].nil?
      puts "  Operation [%d] - %s" % [mutate_result[:index], outcome]
    end
  end
end

def create_keyword_operations(ad_group_id)
  operations = []
  KEYWORD_COUNT.times do |i|
    text = "keyword %d" % i

    # Make 10% of keywords invalid to demonstrate error handling.
    text = text + "!!!" if i % 10 == 0
    keyword = {
      :xsi_type => 'Keyword',
      :text => text,
      :match_type => 'BROAD'
    }
    biddable_criterion = {
      :xsi_type => 'BiddableAdGroupCriterion',
      :ad_group_id => ad_group_id,
      :criterion => keyword
    }
    operation = {
      :xsi_type => 'AdGroupCriterionOperation',
      :operator => 'ADD',
      :operand => biddable_criterion
    }
    operations << operation
  end

  return operations
end

if __FILE__ == $0
  API_VERSION = :v201705
  KEYWORD_COUNT = 100
  MAX_POLL_ATTEMPTS = 5
  PENDING_STATUSES = ['ACTIVE', 'AWAITING_FILE', 'CANCELING']

  begin
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i

    add_keywords_using_incremental_batch_job(ad_group_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Add a draft

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2016, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example illustrates how to create a draft and access its associated
# draft campaign.
#
# See the Campaign Drafts and Experiments guide for more information:
# https://developers.google.com/adwords/api/docs/guides/campaign-drafts-experiments

require 'adwords_api'
require 'date'

def add_draft(base_campaign_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  draft_srv = adwords.service(:DraftService, API_VERSION)

  draft = {
    :base_campaign_id => base_campaign_id,
    :draft_name => 'Test Draft #%d' % (Time.new.to_f * 1000).to_i
  }
  draft_operation = {:operator => 'ADD', :operand => draft}

  draft_result = draft_srv.mutate([draft_operation])

  draft = draft_result[:value].first
  draft_id = draft[:draft_id]
  draft_campaign_id = draft[:draft_campaign_id]

  puts "Draft with id %d and base campaign %d and draft campaign %d created" %
      [draft_id, draft[:base_campaign_id], draft_campaign_id]

  # Once the draft is created, you can modify the draft campaign as if it
  # were a real campaign. For example, you may add criteria, adjust bids,
  # or even include additional ads. Adding a criterion is shown here.
  campaign_criterion_srv =
      adwords.service(:CampaignCriterionService, API_VERSION)

  criterion = {
    :xsi_type => 'Language',
    :id => 1003 # Spanish
  }

  criterion_operation = {
    # Make sure to use the draft_campaign_id when modifying the virtual draft
    # campaign.
    :operator => 'ADD',
    :operand => {
      :campaign_id => draft_campaign_id,
      :criterion => criterion
    }
  }

  criterion_result = campaign_criterion_srv.mutate([criterion_operation])

  criterion = criterion_result[:value].first

  puts "Draft updated to include criteria in campaign %d" % draft_campaign_id
end

if __FILE__ == $0
  API_VERSION = :v201705

  begin
    base_campaign_id = 'INSERT_BASE_CAMPAIGN_ID_HERE'.to_i

    add_draft(base_campaign_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Add a trial

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2016, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example illustrates how to create a trial and wait for it to complete.
#
# See the Campaign Drafts and Experiments guide for more information:
# https://developers.google.com/adwords/api/docs/guides/campaign-drafts-experiments

require 'adwords_api'
require 'date'

def add_trial(draft_id, base_campaign_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  trial_srv = adwords.service(:TrialService, API_VERSION)
  trial_async_error_srv = adwords.service(:TrialAsyncErrorService, API_VERSION)

  trial = {
    :draft_id => draft_id,
    :base_campaign_id => base_campaign_id,
    :name => 'Test Trial #%d' % (Time.new.to_f * 1000).to_i,
    :traffic_split_percent => 50
  }
  trial_operation = {:operator => 'ADD', :operand => trial}

  trial_result = trial_srv.mutate([trial_operation])

  trial_id = trial_result[:value].first[:id]

  # Since creating a trial is asynchronous, we have to poll it to wait for
  # it to finish.
  selector = {
    :fields => ['Id', 'Status', 'BaseCampaignId', 'TrialCampaignId'],
    :predicates => [
      :field => 'Id', :operator => 'IN', :values => [trial_id]
    ]
  }

  poll_attempts = 0
  is_pending = true
  trial = nil
  begin
    sleep_seconds = 30 * (2 ** poll_attempts)
    puts "Sleeping for %d seconds" % sleep_seconds
    sleep(sleep_seconds)

    trial = trial_srv.get(selector)[:entries].first

    puts "Trial ID %d has status '%s'" % [trial[:id], trial[:status]]

    poll_attempts += 1
    is_pending = (trial[:status] == 'CREATING')
  end while is_pending and poll_attempts < MAX_POLL_ATTEMPTS

  if trial[:status] == 'ACTIVE'
    # The trial creation was successful.
    puts "Trial created with id %d and trial campaign id %d" %
        [trial[:id], trial[:trial_campaign_id]]
  elsif trial[:status] == 'CREATION_FAILED'
    # The trial creation failed, and errors can be fetched from the
    # TrialAsyncErrorService.
    selector = {
      :fields => ['TrialId', 'AsyncError'],
      :predicates => [
        {:field => 'TrialId', :operator => 'IN', :values => [trial[:id]]}
      ]
    }

    errors = trial_async_error_srv.get(selector)[:entries]

    if errors.nil?
      puts "Could not retrieve errors for trial %d" % trial[:id]
    else
      puts "Could not create trial due to the following errors:"
      errors.each_with_index do |error, i|
        puts "Error #%d: %s" % [i, error[:async_error]]
      end
    end
  else
    # Most likely, the trial is still being created. You can continue polling,
    # but we have limited the number of attempts in the example.
    puts ("Timed out waiting to create trial from draft %d with base " +
        "campaign %d") % [draft_id, base_campaign_id]
  end
end

if __FILE__ == $0
  API_VERSION = :v201705
  MAX_POLL_ATTEMPTS = 6

  begin
    draft_id = 'INSERT_DRAFT_ID_HERE'.to_i
    base_campaign_id = 'INSERT_BASE_CAMPAIGN_ID_HERE'.to_i

    add_trial(draft_id, base_campaign_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Get all disapproved ads in an ad group

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example illustrates how to retrieve all the disapproved ads in a given
# ad group.

require 'adwords_api'

def get_all_disapproved_ads(ad_group_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  # Get all the ads in this ad group.
  selector = {
    :fields => ['Id', 'PolicySummary'],
    :ordering => [{:field => 'Id', :sort_order => 'ASCENDING'}],
    :predicates => [
      {:field => 'AdGroupId', :operator => 'IN', :values => [ad_group_id]}
    ],
    :paging => {
      :start_index => 0,
      :number_results => PAGE_SIZE
    }
  }

  # Set the initial values.
  offset, page = 0, {}
  disapproved_ads_count = 0

  # Look through all ads to find ones that are disapproved.
  begin
    page = ad_group_ad_srv.get(selector)
    if page[:entries]
      page[:entries].each do |ad_group_ad|
        policy_summary = ad_group_ad[:policy_summary]
        next unless policy_summary[:combined_approval_status] == 'DISAPPROVED'
        disapproved_ads_count += 1
        puts ("Ad with ID %d and type '%s' was disapproved with the " +
            "following policy topic entries:") % [ad_group_ad[:ad][:id],
            ad_group_ad[:ad][:ad_type]]
        policy_summary[:policy_topic_entries].each do |policy_topic_entry|
          puts "  topic id: %s, topic name: '%s'" % [
            policy_topic_entry[:policy_topic_id],
            policy_topic_entry[:policy_topic_name]
          ]
        end
      end
    end
    offset += PAGE_SIZE
    selector[:paging][:start_index] = offset
  end while page[:total_num_entries] > offset

  puts "%d disapproved ads were found." % disapproved_ads_count
end

if __FILE__ == $0
  API_VERSION = :v201705

  PAGE_SIZE = 100

  begin
    # ID of an ad group to get disapproved ads for.
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i
    get_all_disapproved_ads(ad_group_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Get all disapproved ads in an ad group using AWQL

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2012, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example illustrates how to retrieve all the disapproved ads in a given
# ad group with AWQL.

require 'adwords_api'

def get_all_disapproved_ads_with_awql(ad_group_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  # Get all the ads in this ad group.
  query = 'SELECT Id, PolicySummary WHERE AdGroupId = %d ORDER BY Id' %
      ad_group_id

  # Set the initial values.
  offset, page = 0, {}
  disapproved_ads_count = 0

  # Look through all ads to find ones that are disapproved.
  begin
    page_query = query + (" LIMIT %d, %d" % [offset, PAGE_SIZE])
    page = ad_group_ad_srv.query(page_query)
    if page[:entries]
      page[:entries].each do |ad_group_ad|
        policy_summary = ad_group_ad[:policy_summary]
        next unless policy_summary[:combined_approval_status] == 'DISAPPROVED'
        disapproved_ads_count += 1
        puts ("Ad with ID %d and type '%s' was disapproved with the " +
            "following policy topic entries:") % [ad_group_ad[:ad][:id],
            ad_group_ad[:ad][:ad_type]]
        policy_summary[:policy_topic_entries].each do |policy_topic_entry|
          puts "  topic id: %s, topic name: '%s'" % [
            policy_topic_entry[:policy_topic_id],
            policy_topic_entry[:policy_topic_name]
          ]
        end
      end
    end
    offset += PAGE_SIZE
  end while page[:total_num_entries] > offset

  puts "%d disapproved ads were found." % disapproved_ads_count
end

if __FILE__ == $0
  API_VERSION = :v201705

  PAGE_SIZE = 100

  begin
    # ID of an ad group to get disapproved ads for.
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i
    get_all_disapproved_ads_with_awql(ad_group_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts 'HTTP Error: %s' % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts 'Message: %s' % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Get all campaigns with a specific label

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2014, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example gets all campaigns with a specific label. To add a label
# to campaigns, run add_campaign_labels.rb.

require 'adwords_api'

def get_campaigns_by_label(label_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  campaign_srv = adwords.service(:CampaignService, API_VERSION)

  # Get all the ad groups for this campaign.
  selector = {
    :fields => ['Id', 'Name', 'Labels'],
    :ordering => [{:field => 'Name', :sort_order => 'ASCENDING'}],
    :predicates => [
      {
        :field => 'Labels',
        # Labels filtering is performed by ID. You can use CONTAINS_ANY to
        # select campaigns with any of the label IDs, CONTAINS_ALL to select
        # campaigns with all of the label IDs, or CONTAINS_NONE to select
        # campaigns with none of the label IDs.
        :operator => 'CONTAINS_ANY',
        :values => [label_id]
      }
    ],
    :paging => {
      :start_index => 0,
      :number_results => PAGE_SIZE
    }
  }

  # Set initial values.
  offset, page = 0, {}

  begin
    page = campaign_srv.get(selector)
    if page[:entries]
      page[:entries].each do |campaign|
        label_string = campaign[:labels].map do |label|
          '%d/"%s"' % [label[:id], label[:name]]
        end.join(', ')
        puts 'Campaign found with name "%s" and ID %d and labels: %s.' %
            [campaign[:name], campaign[:id], label_string]
      end
      # Increment values to request the next page.
      offset += PAGE_SIZE
      selector[:paging][:start_index] = offset
    end
  end while page[:total_num_entries] > offset
end

if __FILE__ == $0
  API_VERSION = :v201705
  PAGE_SIZE = 500

  begin
    # Label ID to get campaigns for.
    label_id = 'INSERT_LABEL_ID_HERE'.to_i
    get_campaigns_by_label(label_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Graduate a trial

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2016, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example illustrates how to graduate a trial.
#
# See the Campaign Drafts and Experiments guide for more information:
# https://developers.google.com/adwords/api/docs/guides/campaign-drafts-experiments

require 'adwords_api'
require 'date'

def graduate_trial(trial_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  trial_srv = adwords.service(:TrialService, API_VERSION)
  budget_srv = adwords.service(:BudgetService, API_VERSION)

  # To graduate a trial, you must specify a different budget from the base
  # campaign. The base campaign (in order to have had a trial based on it)
  # must have a non-shared budget, so it cannot be shared with the new
  # independent campaign created by graduation.
  budget = {
    :name => 'Budget #%d' % (Time.new.to_f * 1000).to_i,
    :amount => {:micro_amount => 50000000},
    :delivery_method => 'STANDARD'
  }
  budget_operation = {:operator => 'ADD', :operand => budget}

  # Add budget.
  return_budget = budget_srv.mutate([budget_operation])
  budget_id = return_budget[:value].first[:budget_id]

  trial = {
    :id => trial_id,
    :budget_id => budget_id,
    :status => 'GRADUATED'
  }

  trial_operation = {:operator => 'SET', :operand => trial}

  # Update the trial.
  return_trial = trial_srv.mutate([trial_operation])
  trial = return_trial[:value].first

  # Graduation is a synchronous operation, so the campaign is already ready.
  # If you promote instead, make sure to see the polling scheme demonstrated
  # in add_trial.rb to wait for the asynchronous operation to finish.
  puts ("Trial ID %d graduated. Campaign %d was given a new budget ID %d and" +
      "is no longer dependent on this trial.") %
      [trial[:id], trial[:trial_campaign_id], budget_id]
end

if __FILE__ == $0
  API_VERSION = :v201705

  begin
    trial_id = 'INSERT_TRIAL_ID_HERE'.to_i

    graduate_trial(trial_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end


Set ad parameters

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example illustrates how to create a text ad with ad parameters. To add an
# ad group, run add_ad_group.rb. To add a keyword, run add_keywords.rb.

require 'adwords_api'

def set_ad_parameters(ad_group_id, criterion_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)
  ad_param_srv = adwords.service(:AdParamService, API_VERSION)

  # Prepare for adding ad.
  ad_operation = {
    :operator => 'ADD',
    :operand => {
      :ad_group_id => ad_group_id,
      :ad => {
        # The 'xsi_type' field allows you to specify the xsi:type of the object
        # being created. It's only necessary when you must provide an explicit
        # type that the client library can't infer.
        :xsi_type => 'TextAd',
        :headline => 'Luxury Mars Cruises',
        :description1 => 'Low-gravity fun for {param1:cheap}.',
        :description2 => 'Only {param2:a few} seats left!',
        :final_urls => ['http://www.example.com'],
        :display_url => 'www.example.com'
      }
    }
  }

  # Add ad.
  response = ad_group_ad_srv.mutate([ad_operation])
  ad = response[:value].first[:ad]
  puts "Text ad ID %d was successfully added." % ad[:id]

  # Prepare for setting ad parameters.
  price_operation = {
    :operator => 'SET',
    :operand => {
      :ad_group_id => ad_group_id,
      :criterion_id => criterion_id,
      :param_index => 1,
      :insertion_text => '$100'
    }
  }

  seat_operation = {
    :operator => 'SET',
    :operand => {
      :ad_group_id => ad_group_id,
      :criterion_id => criterion_id,
      :param_index => 2,
      :insertion_text => '50'
    }
  }

  # Set ad parameters.
  response = ad_param_srv.mutate([price_operation, seat_operation])
  puts 'Parameters were successfully updated.'
end

if __FILE__ == $0
  API_VERSION = :v201705

  begin
    # IDs of ad group and criterion to set ad parameter for.
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i
    criterion_id = 'INSERT_CRITERION_ID_HERE'.to_i
    set_ad_parameters(ad_group_id, criterion_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Set bid modifier

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2013, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example sets a bid modifier for the mobile platform on given campaign.
# To get campaigns, run get_campaigns.rb.

require 'adwords_api'

def set_criterion_bid_modifier(campaign_id, bid_modifier)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  campaign_criterion_srv =
      adwords.service(:CampaignCriterionService, API_VERSION)

  # Create campaign criterion with modified bid.
  campaign_criterion = {
    :campaign_id => campaign_id,
    # Mobile platform. The ID can be found in the documentation.
    # https://developers.google.com/adwords/api/docs/appendix/platforms
    :criterion => {
      :xsi_type => 'Platform',
      :id => 30001
    },
    :bid_modifier => bid_modifier
  }

  # Create operation.
  operation = {
    :operator => 'SET',
    :operand => campaign_criterion
  }

  response = campaign_criterion_srv.mutate([operation])

  if response and response[:value]
    criteria = response[:value]
    criteria.each do |campaign_criterion|
      criterion = campaign_criterion[:criterion]
      puts ("Campaign criterion with campaign ID %d, criterion ID %d was " +
          "updated with bid modifier %f.") % [campaign_criterion[:campaign_id],
          criterion[:id], campaign_criterion[:bid_modifier]]
    end
  else
    puts 'No criteria were returned.'
  end
end

if __FILE__ == $0
  API_VERSION = :v201705

  begin
    # ID of a campaign to use mobile bid modifier for.
    campaign_id = 'INSERT_CAMPAIGN_ID_HERE'.to_i

    # Bid modifier to set.
    bid_modifier = 1.5

    set_criterion_bid_modifier(campaign_id, bid_modifier)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Validate text ad

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
#
# License:: Licensed under the Apache License, Version 2.0 (the "License");
#           you may not use this file except in compliance with the License.
#           You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#           Unless required by applicable law or agreed to in writing, software
#           distributed under the License is distributed on an "AS IS" BASIS,
#           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
#           implied.
#           See the License for the specific language governing permissions and
#           limitations under the License.
#
# This example shows how to use the 'validate only' header. No objects will be
# created, but exceptions will still be thrown.

require 'adwords_api'

def validate_text_ad(ad_group_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  # Enable 'validate only' option.
  adwords.validate_only = true

  # Prepare for adding text ad.
  operation = {
    :operator => 'ADD',
    :operand => {
      :ad_group_id => ad_group_id,
      :ad => {
        :xsi_type => 'ExpandedTextAd',
        :headline_part1 => 'Luxury Cruise to Mars',
        :headline_part2 => 'Visit the Red Planet in style.',
        :description => 'Low-gravity fun for everyone!',
        :final_urls => ['http://www.example.com']
      }
    }
  }

  # Validate text ad add operation.
  response = ad_group_ad_srv.mutate([operation])
  if response and response[:value]
    ad = response[:value].first
    puts "Unexpected ad creation! Name '%s', ID %d and status '%s'." %
        [campaign[:name], campaign[:id], campaign[:status]]
  else
    puts 'Text ad validated, no error thrown and no ad created.'
  end

  # Now let's check an invalid ad using extra punctuation to trigger an error.
  operation[:operand][:ad][:headline_part1] = 'Luxury Cruise to Mars!!!!!'

  # Validate text ad add operation.
  begin
    response = ad_group_ad_srv.mutate([operation])
    if response and response[:value]
      ad = response[:value].first
      raise StandardError, ("Unexpected ad creation! Name '%s', ID %d and " +
          "status '%s'.") % [campaign[:name], campaign[:id], campaign[:status]]
    end
  rescue AdwordsApi::Errors::ApiException => e
    puts "Validation correctly failed with an exception: %s" % e.class
  end
end

if __FILE__ == $0
  API_VERSION = :v201705

  begin
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i
    validate_text_ad(ad_group_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Send feedback about...

AdWords API
AdWords API
Need help? Visit our support page.