Share your feedback about the Google Ads (AdWords) API. Take the 2021 AdWords API and Google Ads API Annual Survey.

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

Remarketing Samples

The code samples below provide examples of common remarketing functions using the AdWords API. Client Library.

Create a remarketing user list (audience)

#!/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 user list (a.k.a. Audience) and shows
# its associated conversion tracker code snippet.

require 'adwords_api'

def add_audience()
  # 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')

  user_list_srv = adwords.service(:AdwordsUserListService, API_VERSION)
  conv_tracker_srv = adwords.service(:ConversionTrackerService, API_VERSION)

  # Prepare for adding remarketing user list.
  name = "Mars cruise customers #%d" % (Time.new.to_f * 1000).to_i
  operation = {
    :operator => 'ADD',
    :operand => {
      # 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 => 'BasicUserList',
      :name => name,
      :description => 'A list of mars cruise customers in the last year',
      :membership_life_span => 365,
      :conversion_types => [{:name => name}],
      # Optional field.
      :status => 'OPEN'
    }
  }

  # Add user list.
  response = user_list_srv.mutate([operation])
  if response and response[:value]
    user_list = response[:value].first

    # Get conversion snippets.
    conversion_trackers = {}
    if user_list and user_list[:conversion_types]
      conversion_ids = user_list[:conversion_types][:id]
      selector = {
        :fields => ['Id', 'GoogleGlobalSiteTag', 'GoogleEventSnippet'],
        :predicates => [
          {:field => 'Id', :operator => 'IN', :values => [conversion_ids]}
        ]
      }
      conv_tracker_response = conv_tracker_srv.get(selector)
      if conv_tracker_response and conv_tracker_response[:entries]
        conv_tracker_response[:entries].each do |conversion_tracker|
          conversion_trackers[conversion_tracker[:id]] = conversion_tracker
        end
      end
    end
    puts "User list with name '%s' and ID %d was added." %
        [user_list[:name], user_list[:id]]
    # Display the list of associated conversion code snippets.
    user_list_conversion_type = user_list[:conversion_types]
    conversion_tracker =
        conversion_trackers[user_list_conversion_type[:id].to_i]
    puts "Google global site tag:\n%s\n" %
        conversion_tracker[:google_global_site_tag]
    puts "Google event snippet:\n%s\n" %
        conversion_tracker[:google_event_snippet]
  else
    puts 'No user lists were added.'
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    add_audience()

  # 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 an AdWords conversion tracker and add to it upload conversions

#!/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 add an AdWords conversion tracker and an
# upload conversion tracker.

require 'adwords_api'

def add_conversion_trackers()
  # 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')

  conv_tracker_srv = adwords.service(:ConversionTrackerService, API_VERSION)
  # Prepare for adding AdWords conversion tracker.
  operation1 = {
    :operator => 'ADD',
    :operand => {
      # 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 => 'AdWordsConversionTracker',
      :name => "Earth to Mars Cruises Conversion #%d" %
          (Time.new.to_f * 1000).to_i,
      :category => 'DEFAULT',
      # Optional fields:
      :status => 'ENABLED',
      :viewthrough_lookback_window => 15,
      :default_revenue_value => 23.41,
      :always_use_default_revenue_value => true
    }
  }

  # Prepare for adding upload conversion tracker.
  operation2 = {
    :operator => 'ADD',
    :operand => {
      # 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 => 'UploadConversion',

      # Set an appropriate category. This field is optional, and will be set to
      # DEFAULT if not mentioned.
      :category => 'LEAD',
      :name => "Upload Conversion #%d" % (Time.new.to_f * 1000).to_i,
      :viewthrough_lookback_window => 30,
      :ctc_lookback_window => 90,

      # Optional: Set the default currency code to use for conversions that do
      # not specify a conversion currency. This must be an ISO 4217 3-character
      # currency code such as "EUR" or "USD".
      # If this field is not set on this UploadConversion, AdWords will use the
      # account currency.
      :default_revenue_currency_code => 'EUR',

      # Optional: Set the default revenue value to use for conversions that do
      # not specify a conversion value.
      # Note: that this value should NOT be in micros.
      :default_revenue_value => 2.50,

      # Optional: To upload fractional conversion credits, mark the upload
      # conversion as externally attributed. See
      # https://developers.google.com/adwords/api/docs/guides/conversion-tracking#importing_externally_attributed_conversions
      # to learn more about importing externally attributed conversions.
      #:is_externally_attributed => true
    }
  }

  # Add conversion trackers.
  response = conv_tracker_srv.mutate([operation1, operation2])
  if response and response[:value]
    response[:value].each do |tracker|
      puts ("\nConversion tracker with ID %d, name '%s', status '%s' and " +
          "category '%s' was added.") % [tracker[:id], tracker[:name],
          tracker[:status], tracker[:category]]
      if tracker[:xsi_type] == 'AdWordsConversionTracker'
        puts "Google global site tag:\n%s\n" % tracker[:google_global_site_tag]
        puts "Google event snippet:\n%s\n" % tracker[:google_event_snippet]
      end
    end
  else
    puts 'No conversion trackers were added.'
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    add_conversion_trackers()

  # 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 and populate a user list

#!/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 adds a user list (a.k.a. audience) and uploads members to
# populate the list.
#
# Note: It may take several hours for the list to be populated with members.
# Email addresses and other member information must be associated with a Google
# account. For privacy purposes, the user list size will show as zero until the
# list has at least 1000 members. After that, the size will be rounded to the
# two most significant digits.

require 'adwords_api'
require 'digest'

def add_crm_based_user_list()
  # 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')

  user_list_srv = adwords.service(:AdwordsUserListService, API_VERSION)

  user_list = {
    :xsi_type => 'CrmBasedUserList',
    :name => 'Customer relationship management list #%d' % Time.new.usec,
    :description => 'A list of customers that originated from email addresses',
    # CRM-based user lists can use a membershipLifeSpan of 10000 to indicate
    # unlimited; otherwise normal values apply.
    :membership_life_span => 30,
    :upload_key_type => 'CONTACT_INFO'
  }

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

  result = user_list_srv.mutate([operation])
  user_list_id = result[:value].first[:id]

  emails = ['customer1@example.com', 'customer2@example.com',
      ' Customer3@example.com ']

  sha_digest = Digest::SHA256.new
  members = emails.map do |email|
    # Remove leading and trailing whitespace and convert all characters to
    # lowercase before generating the hashed version.
    {
      :hashed_email => sha_digest.hexdigest(normalize_text(email))
    }
  end

  first_name = 'John'
  last_name = 'Doe'
  country_code = 'US'
  zip_code = '10001'

  members << {
    :address_info => {
      # First and last name must be normalized and hashed.
      :hashed_first_name => sha_digest.hexdigest(normalize_text(first_name)),
      :hashed_last_name => sha_digest.hexdigest(normalize_text(last_name)),
      # Country code and zip code are sent in plaintext.
      :country_code => country_code,
      :zip_code => zip_code
    }
  }

  mutate_members_operation = {
    :operand => {
      :user_list_id => user_list_id,
      :members_list => members
    },
    :operator => 'ADD'
  }

  mutate_members_result =
      user_list_srv.mutate_members([mutate_members_operation])

  mutate_members_result[:user_lists].each do |user_list|
    puts "User list with name '%s' and ID '%d' was added." %
        [user_list[:name], user_list[:id]]
  end
end

def normalize_text(text)
  text.strip.downcase
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    add_crm_based_user_list()

  # 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 rule-based user lists

#!/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 two rule-based remarketing user lists: one with no site
# visit data restrictions, and another that will only include users who visit
# your site in the next six months.

require 'adwords_api'

def add_rule_based_user_lists()
  # 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')

  user_list_srv = adwords.service(:AdwordsUserListService, API_VERSION)

  # First rule item group - users who visited the checkout page and had more
  # than one item in their shopping cart.
  cart_rule_item = {
    :xsi_type => 'StringRuleItem',
    :key => {
      :name => 'ecomm_pagetype'
    },
    :op => 'EQUALS',
    :value => 'checkout'
  }

  cart_size_rule_item = {
    :xsi_type => 'NumberRuleItem',
    :key => {
      :name => 'cartsize'
    },
    :op => 'GREATER_THAN',
    :value => 1.0
  }

  # Combine the two rule items into a RuleItemGroup so AdWords will AND
  # their rules together.
  checkout_multiple_item_group = {
    :items => [cart_rule_item, cart_size_rule_item]
  }

  # Second rule item group - users who checked out after October 31st
  # and before January 1st.
  start_date_rule_item = {
    :xsi_type => 'DateRuleItem',
    :key => {
      :name => 'checkoutdate'
    },
    :op => 'AFTER',
    :value => '20141031'
  }

  end_date_rule_item = {
    :xsi_type => 'DateRuleItem',
    :key => {
      :name => 'checkoutdate'
    },
    :op => 'BEFORE',
    :value => '20150101'
  }

  # Combine the date rule items into a RuleItemGroup.
  checked_out_nov_dec_item_group = {
    :items => [start_date_rule_item, end_date_rule_item]
  }

  # Combine the rule item groups into a Rule so AdWords knows how to apply the
  # rules.
  rule = {
    :groups => [checkout_multiple_item_group, checked_out_nov_dec_item_group],
    # ExpressionRuleUserLists can use either CNF or DNF for matching. CNF means
    # 'at least one item in each rule item group must match', and DNF means 'at
    # least one entire rule item group must match'.
    # DateSpecificRuleUserList only supports DNF. You can also omit the rule
    # type altogether to default to DNF.
    :rule_type => 'DNF'
  }

  # Third and fourth rule item groups.
  # Visitors of a page who visited another page.
  sites = ['example.com/example1', 'example.com/example2']

  # Create two rules to show that a visitor browsed two sites.
  user_visited_site_rules = sites.map do |site|
    {
      :groups => [
        :items => [
          {
            :xsi_type => 'StringRuleItem',
            :key => {:name => 'url__'},
            :op => 'EQUALS',
            :value => site
          }
   ]]}
  end

  # Create the user list with no restrictions on site visit date.
  expression_user_list = {
    :xsi_type => 'ExpressionRuleUserList',
    :name => 'Users who checked out in November or December OR ' +
        'visited the checkout page with more than one item in their cart',
    :description => 'Expression based user list',
    :rule => rule,
    # Optional: Set the prepopulationStatus to REQUESTED to include past users
    # in the user list.
    :prepopulation_status => 'REQUESTED'
  }

  # Create the user list restricted to users who visit your site within the
  # specified timeframe.
  date_user_list = {
    :xsi_type => 'DateSpecificRuleUserList',
    :name => 'Date rule user list created at ' + Time.now.to_s,
    :description => 'Users who visited the site between 20141031 and ' +
        '20150331 and checked out in November or December OR visited the ' +
        'checkout page with more than one item in their cart',
    # We re-use the rule here. To avoid side effects, we need a deep copy.
    :rule => Marshal.load(Marshal.dump(rule)),
    # Set the start and end dates of the user list.
    :start_date => '20141031',
    :end_date => '20150331'
  }

  # Create the user list where "Visitors of a page who did visit another page".
  # To create a user list where "Visitors of a page who did not visit another
  # page", change the ruleOperator from AND to AND_NOT.
  combined_rule_user_list = {
    :xsi_type => 'CombinedRuleUserList',
    :name =>
      'Combined rule user list "Visitors of a page who did visit another page"',
    :description => 'Users who visited two sites.',
    :left_operand => user_visited_site_rules[0],
    :right_operand => user_visited_site_rules[1],
    :rule_operator => 'AND'
  }

  # Create operations to add the user lists.
  all_lists = [expression_user_list, date_user_list, combined_rule_user_list]
  operations = all_lists.map do |user_list|
    {
      :operand => user_list,
      :operator => 'ADD'
    }
  end

  # Submit the operations.
  response = user_list_srv.mutate(operations)

  # Display the results.
  response[:value].each do |user_list|
    puts ("User list added with ID %d, name '%s', status '%s', " +
        "list type '%s', accountUserListStatus '%s', description '%s'.") %
        [user_list[:id], user_list[:name], user_list[:status],
        user_list[:list_type], user_list[:account_user_list_status],
        user_list[:description]]
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    add_rule_based_user_lists()

  # 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

Import conversion adjustments for existing conversions

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2018 Google LLC
#
# 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
#
#        https://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 imports conversion adjustments for conversions that already
# exist. To set up a conversion tracker, run the add_conversion_tracker.pl
# example.

require 'adwords_api'

def upload_conversion_adjustments(conversion_name, gclid, adjustment_type,
    conversion_time, adjustment_time, adjusted_value)
  # 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')

  adjustment_srv =
      adwords.service(:OfflineConversionAdjustmentFeedService, API_VERSION)

  # This example demonstrates adjusting one conversion, but you can add more
  # than one operation to adjust more in a single mutate request.
  operations = []

  # Associate conversion adjustments with the existing named conversion tracker.
  # The GCLID should have been uploaded before with a conversion.
  feed = {
    :xsi_type => 'GclidOfflineConversionAdjustmentFeed',
    :conversion_name => conversion_name,
    :google_click_id => gclid,
    :conversion_time => conversion_time,
    :adjustment_type => adjustment_type,
    :adjustment_time => adjustment_time,
    :adjusted_value => adjusted_value
  }

  operations << {
    :operator => 'ADD',
    :operand => feed
  }

  response = adjustment_srv.mutate(operations)
  new_feed = response[:value].first

  puts ('Uploaded offline conversion adjustment value of "%s" for Google ' +
      'Click ID "%s"') %
      [new_feed[:conversion_name], new_feed[:google_click_id]]
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    conversion_name = 'INSERT_CONVERSION_NAME_HERE'
    gclid = 'INSERT_GCLID_HERE'
    adjustment_type = 'INSERT_ADJUSTMENT_TYPE_HERE'
    conversion_time = 'INSERT_CONVERSION_TIME_HERE'
    adjustment_time = 'INSERT_ADJUSTMENT_TIME_HERE'
    adjusted_value = 'INSERT_ADJUSTED_VALUE_HERE'.to_f

    upload_conversion_adjustments(conversion_name, gclid, adjustment_type,
        conversion_time, adjustment_time, adjusted_value)

  # 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

Import offline call conversions

#!/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 code example imports offline conversion values for calls related to ads
# in your account.
#
# To set up a conversion tracker, run the add_conversion_trackers.rb example.

require 'adwords_api'

def upload_offline_call_conversions(caller_id, call_start_time, conversion_name,
    conversion_time, conversion_value)
  # 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')

  occ_feed_srv =
      adwords.service(:OfflineCallConversionFeedService, API_VERSION)

  # Associate offline conversions with the existing named conversion tracker. If
  # this tracker was newly created, it may be a few hours before it can accept
  # conversions.
  feed = {
    :caller_id => caller_id,
    :call_start_time => call_start_time,
    :conversion_name => conversion_name,
    :conversion_time => conversion_time,
    :conversion_value => conversion_value
  }

  occ_operations = [{
    :operator => 'ADD',
    :operand => feed
  }]

  occ_response = occ_feed_srv.mutate(occ_operations)

  if occ_response[:value]
    occ_response[:value].each do |occ_feed|
      puts 'Uploaded offline call conversion value "%s" for caller ID "%s"' %
          [occ_feed[:conversion_name], occ_feed[:caller_id]]
    end
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    caller_id = 'INSERT_CALLER_ID_HERE'
    # For times, use the format yyyyMMdd HHmmss tz. For more details on
    # formats, see:
    # https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
    # For time zones, see:
    # https://developers.google.com/adwords/api/docs/appendix/codes-formats#timezone-ids
    call_start_time = 'INSERT_CALL_START_TIME_HERE'
    conversion_name = 'INSERT_CONVERSION_NAME_HERE'
    conversion_time = 'INSERT_CONVERSION_TIME_HERE'
    conversion_value = 'INSERT_CONVERSION_VALUE_HERE'

    upload_offline_call_conversions(
        caller_id,
        call_start_time,
        conversion_name,
        conversion_time,
        conversion_value
    )

  # 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

Import offline click conversions

#!/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 code example imports offline conversion values for specific clicks to
# your account. To get Google Click ID for a click, run
# CLICK_PERFORMANCE_REPORT. To set up a conversion tracker, run the
# add_conversion_trackers.rb example.

require 'adwords_api'
require 'date'

def upload_offline_conversions(conversion_name, google_click_id,
                               conversion_time, conversion_value)
  # 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')

  conversion_feed_srv =
      adwords.service(:OfflineConversionFeedService, API_VERSION)

  # Associate offline conversions with the existing named conversion tracker. If
  # this tracker was newly created, it may be a few hours before it can accept
  # conversions.
  feed = {
    :conversion_name => conversion_name,
    :google_click_id => google_click_id,
    :conversion_time => conversion_time,
    :conversion_value => conversion_value
  }

  # Optional: To upload fractional conversion credits, set the external
  # attribution model and credit. To use this feature, your conversion tracker
  # should be marked as externally attributed. See
  # https://developers.google.com/adwords/api/docs/guides/conversion-tracking#importing_externally_attributed_conversions
  # to learn more about importing externally attributed conversions.
  #
  # feed[:external_attribution_model] = "Linear"
  # feed[:external_attribution_credit] = 0.3

  return_feeds = conversion_feed_srv.mutate([
    {:operator => 'ADD', :operand => feed}])
  return_feeds[:value].each do |return_feed|
    puts ("Uploaded offline conversion value %.2f for Google Click ID '%s', " +
        'to %s') % [return_feed[:conversion_value],
                    return_feed[:google_click_id],
                    return_feed[:conversion_name]]
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    # Name of the conversion tracker to upload to.
    conversion_name = 'INSERT_CONVERSION_NAME_HERE'
    # Google Click ID of the click for which offline conversions are uploaded.
    google_click_id = 'INSERT_GOOGLE_CLICK_ID_HERE'
    # Conversion time in 'yyyymmdd hhmmss' format.
    conversion_time = Time.new.strftime("%Y%m%d %H%M%S")
    # Conversion value to be uploaded.
    conversion_value = 'INSERT_CONVERSION_VALUE_HERE'.to_f

    upload_offline_conversions(conversion_name, google_click_id,
                               conversion_time, conversion_value)

  # 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 offline data for store sales transactions

#!/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 shows how to upload offline data for store sales
# transactions.

require 'adwords_api'
require 'digest'
require 'date'

def upload_offline_data(conversion_name, external_upload_id,
    store_sales_upload_common_metadata_type, email_addresses,
    advertiser_upload_time, bridge_map_version_id, partner_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')

  unless email_addresses.size == 2
    raise ArgumentError, ('%d email addresses specified. Please specify ' +
        'exactly 2 email addresses.') % email_addresses.size
  end

  # Set partial failure to true since this example demonstrates how to handle
  # partial failure errors.
  adwords.partial_failure = true
  offline_data_upload_srv = adwords.service(
      :OfflineDataUploadService, API_VERSION)

  offline_data_list = []

  # Create the first offline data for upload.
  # This transaction occurred 7 days ago with amount of 200 USD.
  transaction_time_1 = DateTime.parse((Date.today - 7).to_s)
  transaction_amount_1 = 200_000_000
  user_identifiers_1 = [
    create_user_identifier('HASHED_EMAIL', email_addresses[0]),
    create_user_identifier('STATE', 'New York')
  ]

  offline_data_list << create_offline_data(transaction_time_1,
      transaction_amount_1, 'USD', conversion_name, user_identifiers_1)

  # Create the second offline data for upload.
  # This transaction occurred 14 days ago with amount of 450 EUR.
  transaction_time_2 = DateTime.parse((Date.today - 14).to_s)
  transaction_amount_2 = 450_000_000
  user_identifiers_2 = [
    create_user_identifier('HASHED_EMAIL', email_addresses[1]),
    create_user_identifier('STATE', 'California')
  ]

  offline_data_list << create_offline_data(transaction_time_2,
      transaction_amount_2, 'EUR', conversion_name, user_identifiers_2)

  # Set the type and metadata of this upload.
  upload_metadata = {
    :xsi_type => store_sales_upload_common_metadata_type,
    :loyalty_rate => 1.0,
    :transaction_upload_rate => 1.0
  }

  upload_type = 'STORE_SALES_UPLOAD_FIRST_PARTY'
  if store_sales_upload_common_metadata_type == METADATA_TYPE_3P
    upload_type = 'STORE_SALES_UPLOAD_THIRD_PARTY'
    upload_metadata[:advertiser_upload_time] = advertiser_upload_time
    upload_metadata[:valid_transaction_rate] = 1.0
    upload_metadata[:partner_match_rate] = 1.0
    upload_metadata[:partner_upload_rate] = 1.0
    upload_metadata[:bridge_map_version_id] = bridge_map_version_id
    upload_metadata[:partner_id] = partner_id
  end

  # Create offline data upload object.
  offline_data_upload = {
    :external_upload_id => external_upload_id,
    :offline_data_list => offline_data_list,
    :upload_type => upload_type,
    :upload_metadata => upload_metadata
  }

  # Create an offline data upload operation.
  data_upload_operation = {
    :operator => 'ADD',
    :operand => offline_data_upload
  }

  # Upload offline data on the server and print some information.
  operations = [data_upload_operation]
  return_value = offline_data_upload_srv.mutate(operations)
  offline_data_upload = return_value[:value].first
  puts ('Uploaded offline data with external upload ID %d, and upload ' +
      'status %s') % [offline_data_upload[:external_upload_id],
      offline_data_upload[:upload_status]]

  # Print any partial data errors from the response.
  unless return_value[:partial_failure_errors].nil?
    return_value[:partial_failure_errors].each do |api_error|
      # Get the index of the failed operation from the error's field path
      # elements.
      operation_index = get_field_path_element_index(api_error, 'operations')
      unless operation_index.nil?
        failed_offline_data_upload = operations[operation_index][:operand]
        # Get the index of the entry in the offline data list from the error's
        # field path elements.
        offline_data_list_index = get_field_path_element_index(api_error,
            'offlineDataList')
        puts ("Offline data list entry %d in operation %d with external " +
            "upload ID %d and type '%s' has triggered failure for the " +
            " following reason: '%s'") %
            [offline_data_list_index, operation_index,
            failed_offline_data_upload[:external_upload_id],
            failed_offline_data_upload[:upload_type], api_error[:error_string]]
      else
        puts "A failure has occurred for the following reason: '%s'" %
            apiError[:error_string]
      end
    end
  end
end

def get_field_path_element_index(api_error, field)
  field_path_elements = api_error[:field_path_elements]
  return nil if field_path_elements.nil?
  field_path_elements.each_with_index do |field_path_element, i|
    if field == field_path_element[:field]
      return field_path_element[:index]
    end
  end
  return nil
end

def create_offline_data(transaction_time, transaction_micro_amount,
    transaction_currency, conversion_name, user_identifiers)
  store_sales_transaction = {
    :xsi_type => 'StoreSalesTransaction',
    # For times, use the format yyyyMMdd HHmmss [tz].
    # For details, see
    # https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
    :transaction_time => transaction_time.strftime('%Y%m%d %H%M%S'),
    :conversion_name => conversion_name,
    :user_identifiers => user_identifiers,
    :transaction_amount => {
      :money => {
        :micro_amount => transaction_micro_amount
      },
      :currency_code => transaction_currency
    }
  }
  return store_sales_transaction
end

def create_user_identifier(identifier_type, value)
  # If the user identifier type is a hashed type, also call the hash function
  # on the value.
  if HASHED_IDENTIFIER_TYPES.include?(identifier_type)
    value = hash_string(value)
  end
  user_identifier = {
    :user_identifier_type => identifier_type,
    :value => value
  }
  return user_identifier
end

def hash_string(text)
  sha_digest = Digest::SHA256.new
  sha_digest.hexdigest(normalize_text(text))
end

def normalize_text(text)
  text.strip.downcase
end

if __FILE__ == $0
  API_VERSION = :v201809

  HASHED_IDENTIFIER_TYPES = [
    'HASHED_EMAIL',
    'HASHED_FIRST_NAME',
    'HASHED_LAST_NAME',
    'HASHED_PHONE'
  ]

  # Store sales upload common metadata types
  METADATA_TYPE_1P = 'FirstPartyUploadMetadata'
  METADATA_TYPE_3P = 'ThirdPartyUploadMetadata'

  begin
    conversion_name = 'INSERT_CONVERSION_NAME_HERE'
    external_upload_id = 'INSERT_EXTERNAL_UPLOAD_ID_HERE'.to_i
    email_addresses = ['INSERT_EMAIL_ADDRESS_HERE', 'INSERT_EMAIL_ADDRESS_HERE']

    # Set the below constant to METADATA_TYPE_3P if uploading third-party data.
    store_sales_upload_common_metadata_type = METADATA_TYPE_1P

    # The three constants below are needed when uploading third-party data. They
    # are not used when uploading first-party data.
    # Advertiser upload time to partner.
    # For times, use the format yyyyMMdd HHmmss tz. For more details on formats,
    # see:
    # https://developers.google.com/adwords/api/docs/appendix/codes-formats#timezone-ids
    advertiser_upload_time = 'INSERT_ADVERTISER_UPLOAD_TIME_HERE'
    bridge_map_version_id = 'INSERT_BRIDGE_MAP_VERSION_ID_HERE'
    partner_id = 'INSERT_PARTNER_ID_HERE'.to_i

    upload_offline_data(conversion_name, external_upload_id,
        store_sales_upload_common_metadata_type, email_addresses,
        advertiser_upload_time, bridge_map_version_id, partner_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