The AdWords API will sunset on April 27, 2022. Migrate to the Google Ads API to take advantage of the latest Google Ads features.

Campaign Management Samples

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

Add a campaign group and set its performance target

#!/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 = :v201809

  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 = :v201809

  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 a campaign using BatchJobService

#!/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'
        }
      }
    }
    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 = :v201809
  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

Create a draft and access its campaign

#!/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 = :v201809

  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

Upload keywords incrementally using BatchJobService

#!/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 = :v201809
  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

Create 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,
    :traffic_split_type => 'RANDOM_QUERY'
  }
  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 = :v201809
  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]},
      {
        :field => 'CombinedApprovalStatus',
        :operator => 'IN',
        :values => ['DISAPPROVED']
      }
    ],
    :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]
        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', Help Center URL: '%s'" % [
            policy_topic_entry[:policy_topic_id],
            policy_topic_entry[:policy_topic_name],
            policy_topic_entry[:policy_topic_help_center_url]
          ]
          unless policy_topic_entry[:policy_topic_evidences].nil?
            policy_topic_entry[:policy_topic_evidences].each do |evidence|
              puts "    evidence type: '%s'" %
                  evidence[:policy_topic_evidence_type]
              unless evidence[:evidence_text_list].nil?
                evidence[:evidence_text_list].each_with_index do |text, i|
                  puts "      evidence text[%d]: '%s'" % [i, text]
                end
              end
            end
          end
        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 = :v201809

  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_builder = adwords.service_query_builder do |b|
    b.select('Id', 'PolicySummary')
    b.where('AdGroupId').equal_to(ad_group_id)
    b.where('CombinedApprovalStatus').equal_to('DISAPPROVED')
    b.order_by_asc('Id')
    b.limit(0, PAGE_SIZE)
  end
  query = query_builder.build

  # Set the initial values.
  disapproved_ads_count = 0

  # Look through all ads to find ones that are disapproved.
  loop do
    page_query = query.to_s
    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]
        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', Help Center URL: '%s'" % [
            policy_topic_entry[:policy_topic_id],
            policy_topic_entry[:policy_topic_name],
            policy_topic_entry[:policy_topic_help_center_url]
          ]
          unless policy_topic_entry[:policy_topic_evidences].nil?
            policy_topic_entry[:policy_topic_evidences].each do |evidence|
              puts "    evidence type: '%s'" %
                  evidence[:policy_topic_evidence_type]
              unless evidence[:evidence_text_list].nil?
                evidence[:evidence_text_list].each_with_index do |text, i|
                  puts "      evidence text[%d]: '%s'" % [i, text]
                end
              end
            end
          end
        end
      end
    end
    break unless query.has_next(page)
    query.next_page
  end

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

if __FILE__ == $0
  API_VERSION = :v201809

  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 = :v201809
  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 = :v201809

  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 for a keyword ad group criterion

#!/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 = :v201809

  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 a bid modifier on a campaign

#!/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 = :v201809

  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 through setValidateOnly header

#!/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 = :v201809

  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