Report Streaming

There are three ways to retrieve entities and reporting data with Google Ads API.

This guide primarily focuses on streaming data from GoogleAdsService. Here are high-level distinctions for the three data retrieval methods:

GET requests GoogleAdsService.Search GoogleAdsService.SearchStream
Suitable for production code No, debugging only Yes Yes
Service Resource specific services (e.g., CampaignService) GoogleAdsService GoogleAdsService
Scenario Fetching objects Fetching objects and reports Fetching objects and reports
Response One object (e.g., Campaign) Pages of GoogleAdsRow objects Stream of GoogleAdsRow objects
Response's fields All fields populated Only those specified in the query Only those specified in the query
Daily limits 1,000 requests per day Daily limits based on access levels Daily limits based on access levels

Search vs SearchStream

While Search may send multiple paginated requests to download the entire report, SearchStream sends a single request and initiates a persistent connection with the Google Ads API regardless of the report size. Packets of data will start to download immediately while the entire remaining result is cached in a data buffer. Your code can start reading the buffered data without waiting for the entire stream to finish. By eliminating the round-trip network time required to request each individual page of a Search response, depending on your application, SearchStream may offer improved performance over paging. This advantage becomes more apparent as reports grow much larger. Here is an example to show the internal mechanics between the two mechanisms:

Search SearchStream
Report size 100,000 rows 100,000 rows
Page Size 10,000 rows per page Not Applicable
Number of API requests 10 requests 1 request
Number of API responses 10 responses 1 continuous stream

You are also likely to get the first set of results faster with streaming, since Search initially caches the entire result set before sending the first page over to the client.

If you have an app that requires paging, Search may be a better choice since it does not require downloading the entire result set, while streaming would require downloading it entirely. Also, if a stream is interrupted or experiences a failure in the middle of a download, your platform would have to restart the entire download again from the beginning.

Rate limit

Daily limits with both Search and SearchStream adhere to the standard limits and access levels of your developer token. A single query or report using either Search or SearchStream is counted as one operation, irrespective if the result is paged or streamed.

Code examples

Java
private void runExample(GoogleAdsClient googleAdsClient, long customerId) {
  try (GoogleAdsServiceClient googleAdsServiceClient =
      googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
    String searchQuery =
        "SELECT campaign.id, "
            + "campaign.name, "
            + "ad_group.id, "
            + "ad_group.name, "
            + "ad_group_criterion.criterion_id, "
            + "ad_group_criterion.keyword.text, "
            + "ad_group_criterion.keyword.match_type, "
            + "metrics.impressions, "
            + "metrics.clicks, "
            + "metrics.cost_micros "
            + "FROM keyword_view "
            + "WHERE segments.date DURING LAST_7_DAYS "
            + "AND campaign.advertising_channel_type = 'SEARCH' "
            + "AND ad_group.status = 'ENABLED' "
            + "AND ad_group_criterion.status IN ('ENABLED', 'PAUSED') "
            // Limits to the 50 keywords with the most impressions in the date range.
            + "ORDER BY metrics.impressions DESC "
            + "LIMIT 50";
    // Constructs the SearchGoogleAdsStreamRequest.
    SearchGoogleAdsStreamRequest request =
        SearchGoogleAdsStreamRequest.newBuilder()
            .setCustomerId(Long.toString(customerId))
            .setQuery(searchQuery)
            .build();

    // Creates and issues a search Google Ads stream request that will retrieve all of the
    // requested field values for the keyword.
    ServerStream<SearchGoogleAdsStreamResponse> stream =
        googleAdsServiceClient.searchStreamCallable().call(request);

    // Iterates through the results in the stream response and prints all of the requested
    // field values for the keyword in each row.
    for (SearchGoogleAdsStreamResponse response : stream) {
      for (GoogleAdsRow googleAdsRow : response.getResultsList()) {
        Campaign campaign = googleAdsRow.getCampaign();
        AdGroup adGroup = googleAdsRow.getAdGroup();
        AdGroupCriterion adGroupCriterion = googleAdsRow.getAdGroupCriterion();
        Metrics metrics = googleAdsRow.getMetrics();

        System.out.printf(
            "Keyword text '%s' with "
                + "match type '%s' "
                + "and ID %d "
                + "in ad group '%s' "
                + "with ID %d "
                + "in campaign '%s' "
                + "with ID %d "
                + "had %d impression(s), "
                + "%d click(s), "
                + "and %d cost (in micros) "
                + "during the last 7 days.%n",
            adGroupCriterion.getKeyword().getText(),
            adGroupCriterion.getKeyword().getMatchType(),
            adGroupCriterion.getCriterionId(),
            adGroup.getName(),
            adGroup.getId(),
            campaign.getName(),
            campaign.getId(),
            metrics.getImpressions(),
            metrics.getClicks(),
            metrics.getCostMicros());
      }
    }
  }
}
C#
public void Run(GoogleAdsClient client, long customerId)
{
    // Get the GoogleAdsService.
    GoogleAdsServiceClient googleAdsService = client.GetService(
        Services.V6.GoogleAdsService);

    // Create the query.
    string query =
        @"SELECT
         campaign.id,
         campaign.name,
         ad_group.id,
         ad_group.name,
         ad_group_criterion.criterion_id,
         ad_group_criterion.keyword.text,
         ad_group_criterion.keyword.match_type,
         metrics.impressions,
         metrics.clicks,
         metrics.cost_micros
     FROM keyword_view
     WHERE segments.date DURING LAST_7_DAYS
         AND campaign.advertising_channel_type = 'SEARCH'
         AND ad_group.status = 'ENABLED'
         AND ad_group_criterion.status IN ('ENABLED','PAUSED')
     ORDER BY metrics.impressions DESC
     LIMIT 50";

    try
    {
        // Issue a search request.
        googleAdsService.SearchStream(customerId.ToString(), query,
            delegate (SearchGoogleAdsStreamResponse resp)
            {
                // Display the results.
                foreach (GoogleAdsRow criterionRow in resp.Results)
                {
                    Console.WriteLine(
                        "Keyword with text " +
                        $"'{criterionRow.AdGroupCriterion.Keyword.Text}', match type " +
                        $"'{criterionRow.AdGroupCriterion.Keyword.MatchType}' and ID " +
                        $"{criterionRow.AdGroupCriterion.CriterionId} in ad group " +
                        $"'{criterionRow.AdGroup.Name}' with ID " +
                        $"{criterionRow.AdGroup.Id} in campaign " +
                        $"'{criterionRow.Campaign.Name}' with ID " +
                        $"{criterionRow.Campaign.Id} had " +
                        $"{criterionRow.Metrics.Impressions.ToString()} impressions, " +
                        $"{criterionRow.Metrics.Clicks} clicks, and " +
                        $"{criterionRow.Metrics.CostMicros} cost (in micros) during the " +
                        "last 7 days.");
                }
            }
        );
    }
    catch (GoogleAdsException e)
    {
        Console.WriteLine("Failure:");
        Console.WriteLine($"Message: {e.Message}");
        Console.WriteLine($"Failure: {e.Failure}");
        Console.WriteLine($"Request ID: {e.RequestId}");
        throw;
    }
}
PHP
public static function runExample(GoogleAdsClient $googleAdsClient, int $customerId)
{
    $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
    // Creates a query that retrieves all keyword statistics.
    $query =
        "SELECT campaign.id, "
            . "campaign.name, "
            . "ad_group.id, "
            . "ad_group.name, "
            . "ad_group_criterion.criterion_id, "
            . "ad_group_criterion.keyword.text, "
            . "ad_group_criterion.keyword.match_type, "
            . "metrics.impressions, "
            . "metrics.clicks, "
            . "metrics.cost_micros "
        . "FROM keyword_view "
        . "WHERE segments.date DURING LAST_7_DAYS "
            . "AND campaign.advertising_channel_type = 'SEARCH' "
            . "AND ad_group.status = 'ENABLED' "
            . "AND ad_group_criterion.status IN ('ENABLED', 'PAUSED') "
        // Limits to the 50 keywords with the most impressions in the date range.
        . "ORDER BY metrics.impressions DESC "
        . "LIMIT 50";

    // Issues a search stream request.
    /** @var GoogleAdsServerStreamDecorator $stream */
    $stream =
        $googleAdsServiceClient->searchStream($customerId, $query);

    // Iterates over all rows in all messages and prints the requested field values for
    // the keyword in each row.
    foreach ($stream->iterateAllElements() as $googleAdsRow) {
        /** @var GoogleAdsRow $googleAdsRow */
        $campaign = $googleAdsRow->getCampaign();
        $adGroup = $googleAdsRow->getAdGroup();
        $adGroupCriterion = $googleAdsRow->getAdGroupCriterion();
        $metrics = $googleAdsRow->getMetrics();
        printf(
            "Keyword text '%s' with "
            . "match type %s "
            . "and ID %d "
            . "in ad group '%s' "
            . "with ID %d "
            . "in campaign '%s' "
            . "with ID %d "
            . "had %d impression(s), "
            . "%d click(s), "
            . "and %d cost (in micros) "
            . "during the last 7 days.%s",
            $adGroupCriterion->getKeyword()->getText(),
            KeywordMatchType::name($adGroupCriterion->getKeyword()->getMatchType()),
            $adGroupCriterion->getCriterionId(),
            $adGroup->getName(),
            $adGroup->getId(),
            $campaign->getName(),
            $campaign->getId(),
            $metrics->getImpressions(),
            $metrics->getClicks(),
            $metrics->getCostMicros(),
            PHP_EOL
        );
    }
}
Python
def main(client, customer_id):
    ga_service = client.get_service("GoogleAdsService", version="v6")

    query = """
        SELECT
          campaign.id,
          campaign.name,
          ad_group.id,
          ad_group.name,
          ad_group_criterion.criterion_id,
          ad_group_criterion.keyword.text,
          ad_group_criterion.keyword.match_type,
          metrics.impressions,
          metrics.clicks,
          metrics.cost_micros
        FROM keyword_view
        WHERE
          segments.date DURING LAST_7_DAYS
          AND campaign.advertising_channel_type = 'SEARCH'
          AND ad_group.status = 'ENABLED'
          AND ad_group_criterion.status IN ('ENABLED', 'PAUSED')
        ORDER BY metrics.impressions DESC
        LIMIT 50"""

    # Issues a search request using streaming.
    response = ga_service.search_stream(customer_id, query)
    keyword_match_type_enum = client.get_type(
        "KeywordMatchTypeEnum", version="v6"
    ).KeywordMatchType
    try:
        for batch in response:
            for row in batch.results:
                campaign = row.campaign
                ad_group = row.ad_group
                criterion = row.ad_group_criterion
                metrics = row.metrics
                keyword_match_type = keyword_match_type_enum.Name(
                    criterion.keyword.match_type
                )
                print(
                    f'Keyword text "{criterion.keyword.text}" with '
                    f'match type "{keyword_match_type}" '
                    f"and ID {criterion.criterion_id} in "
                    f'ad group "{ad_group.name}" '
                    f'with ID "{ad_group.id}" '
                    f'in campaign "{campaign.name}" '
                    f"with ID {campaign.id} "
                    f"had {metrics.impressions} impression(s), "
                    f"{metrics.clicks} click(s), and "
                    f"{metrics.cost_micros} cost (in micros) during "
                    "the last 7 days."
                )
    except GoogleAdsException as ex:
        print(
            f'Request with ID "{ex.request_id}" failed with status '
            f'"{ex.error.code().name}" and includes the following errors:'
        )
        for error in ex.failure.errors:
            print(f'\tError with message "{error.message}".')
            if error.location:
                for field_path_element in error.location.field_path_elements:
                    print(f"\t\tOn field: {field_path_element.field_name}")
        sys.exit(1)
Ruby
def get_keyword_stats(customer_id)
  # GoogleAdsClient will read a config file from
  # ENV['HOME']/google_ads_config.rb when called without parameters
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  ga_service = client.service.google_ads

  # Limits to the 50 keywords with the most impressions in the date range.
  # If you wish to exclude entries with zero impressions, include a
  # predicate in the WHERE statement like 'metrics.impressions > 0'
  query = <<~QUERY
    SELECT campaign.id,
           campaign.name,
           ad_group.id,
           ad_group.name,
           ad_group_criterion.criterion_id,
           ad_group_criterion.keyword.text,
           ad_group_criterion.keyword.match_type,
           metrics.impressions,
           metrics.clicks,
           metrics.cost_micros
    FROM keyword_view
    WHERE segments.date DURING LAST_7_DAYS
      AND campaign.advertising_channel_type = 'SEARCH'
      AND ad_group.status = 'ENABLED'
      AND ad_group_criterion.status IN ('ENABLED', 'PAUSED')
    ORDER BY metrics.impressions DESC
    LIMIT 50
  QUERY

  responses = ga_service.search_stream(customer_id: customer_id, query: query)

  responses.each do |response|
    response.results.each do |row|
      campaign = row.campaign
      ad_group = row.ad_group
      criterion = row.ad_group_criterion
      metrics = row.metrics

      puts "Keyword text '#{criterion.keyword.text}' with match type "\
        "'#{criterion.keyword.match_type}' and ID #{criterion.criterion_id} in "\
        "ad group '#{ad_group.name}' with ID #{ad_group.id} in campaign "\
        "'#{campaign.name}' with ID #{campaign.id} had #{metrics.impressions} "\
        "impression(s), #{metrics.clicks} click(s), and #{metrics.cost_micros} "\
        "cost (in micros) during the last 7 days."
    end
  end
end
Perl
sub get_keyword_stats {
  my ($api_client, $customer_id) = @_;

  # Limit to the 50 keywords with the most impressions in the date range.
  # If you wish to exclude entries with zero impressions, include a
  # predicate in the WHERE statement like 'metrics.impressions > 0'.
  my $search_query =
    "SELECT campaign.id, campaign.name, ad_group.id, ad_group.name, " .
    "ad_group_criterion.criterion_id, ad_group_criterion.keyword.text, " .
    "ad_group_criterion.keyword.match_type, " .
    "metrics.impressions, metrics.clicks, metrics.cost_micros " .
    "FROM keyword_view WHERE segments.date DURING LAST_7_DAYS " .
    "AND campaign.advertising_channel_type = 'SEARCH' " .
    "AND ad_group.status = 'ENABLED' " .
    "AND ad_group_criterion.status IN ('ENABLED', 'PAUSED') " .
    "ORDER BY metrics.impressions DESC LIMIT 50";

  # Create a search Google Ads stream request that will retrieve all keyword
  # statistics.
  my $search_stream_request =
    Google::Ads::GoogleAds::V6::Services::GoogleAdsService::SearchGoogleAdsStreamRequest
    ->new({
      customerId => $customer_id,
      query      => $search_query,
    });

  # Get the GoogleAdsService.
  my $google_ads_service = $api_client->GoogleAdsService();

  my $search_stream_handler =
    Google::Ads::GoogleAds::Utils::SearchStreamHandler->new({
      service => $google_ads_service,
      request => $search_stream_request
    });

  # Issue a search request and process the stream response to print the requested
  # field values for the keyword in each row.
  $search_stream_handler->process_contents(
    sub {
      my $google_ads_row     = shift;
      my $campaign           = $google_ads_row->{campaign};
      my $ad_group           = $google_ads_row->{adGroup};
      my $ad_group_criterion = $google_ads_row->{adGroupCriterion};
      my $metrics            = $google_ads_row->{metrics};

      printf "Keyword text '%s' with match type '%s' and ID %d in ad group" .
        " '%s' with ID %d in campaign '%s' with ID %d had %d impression(s), " .
        "%d click(s), and %d cost (in micros) during the last 7 days.\n",
        $ad_group_criterion->{keyword}{text},
        $ad_group_criterion->{keyword}{matchType},
        $ad_group_criterion->{criterionId},
        $ad_group->{name},
        $ad_group->{id},
        $campaign->{name},
        $campaign->{id},
        $metrics->{impressions},
        $metrics->{clicks},
        $metrics->{costMicros};
    });

  return 1;
}