**Important:** This feature is available to allowlisted accounts only. Contact your Google representative if you need access to the feature.

<br />

Once you've gathered the targeting settings and a product mix for your media
plan, you can generate a curve of budgets versus potential reach using the
[`GenerateReachForecast`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/GenerateReachForecast.md.txt) method in
[`ReachPlanService`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService.md.txt). See the
[glossary](https://developers.google.com/google-ads/api/docs/reach-forecasting/concepts.md.txt#glossary-of-terms) and [media plan
guide](https://developers.google.com/google-ads/api/docs/reach-forecasting/media-plan) for details on the request fields.

> [!NOTE]
> **Note:** Within the `targeting.audience_targeting` ([`AudienceTargeting`](https://developers.google.com/google-ads/api/reference/rpc/v26/AudienceTargeting.md.txt)) field, certain `user_interest_category` values in [`UserInterestInfo`](https://developers.google.com/google-ads/api/reference/rpc/v26/UserInterestInfo.md.txt) are not supported for reach forecasts. Use [`ReachPlanService.ListPlannableUserInterests`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/ListPlannableUserInterests.md.txt) and [`ReachPlanService.ListPlannableUserLists`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/ListPlannableUserLists.md.txt) to query supported criteria before calling `GenerateReachForecast`.

## Forecast metrics

The response from `GenerateReachForecast` includes various metrics within the
[`Forecast`](https://developers.google.com/google-ads/api/reference/rpc/v26/Forecast.md.txt) message, providing insights into the
potential performance of your planned campaign. These metrics include:

- Reach (on-target and total, including co-view)
- Impressions (on-target and total, including co-view, and viewable)
- Effective frequency breakdowns (how many unique users are reached at least a certain number of times; for example, 1+, 2+, or 3+ impressions)
- User actions (TrueView views, conversions, and clicks)

## Forecast reach for Demand Gen

When generating curves for `DEMAND_GEN` plannable products, you must also
include an expected conversion rate for each product.

To obtain conversion rate suggestions for supported plannable products, use the
[`GenerateConversionRates`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/GenerateConversionRates.md.txt) method. This method
returns conversion rate suggestions based on either historical performance for
your account or suggested rates based on performance for all advertisers.

For `DEMAND_GEN` products, `GenerateConversionRates` may return multiple
suggestions for the same product. Some suggestions have the `surface_targeting`
field populated, which indicates that the suggestion applies only to that
specific combination of surfaces (for example, YouTube Shorts). If
`surface_targeting` is not populated, the suggestion is a Demand Gen campaign
average for that product. When generating forecasts for Demand Gen campaigns
with surface targeting, use the most specific conversion rate suggestion
available.

In addition to conversion rates, Demand Gen users can use
`GenerateReachForecast` to project `clicks`, which is particularly relevant for
campaigns optimizing for clicks, such as Demand Gen with Max Clicks bidding.

## Forecast reach for YouTube Select

YouTube Select (YTS) in `ReachPlanService` supports lineup-level targeting for
all eligible YTS locations on Instant Reserve pricing. Use the
`youtube_select_lineup_targeting` field in
[`ReachPlanService.ListPlannableProducts`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanService/ListPlannableProducts.md.txt) to identify
available lineups (`youtube_select_lineup_targeting` replaced the
`youtube_select_lineups` field, which was deprecated in `v23` and removed in
`v24`).

With Instant Reserve, the following errors can occur when requesting a forecast
for YTS:

- [Not enough inventory available](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanErrorEnum.ReachPlanError.md.txt#not_forecastable_not_enough_inventory)
- [Non-spending accounts disabled](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanErrorEnum.ReachPlanError.md.txt#not_forecastable_account_not_enabled)

## Save curves

You can only save individual curves to provide convenience for your users.
Caching curves en masse is strictly prohibited. If you need to generate curves
en masse, contact your Google account representative first.

## Error handling

Requests to `ReachPlanService` must follow the [basic API
structure](https://developers.google.com/google-ads/api/docs/concepts/call-structure) and may return [common
errors](https://developers.google.com/google-ads/api/docs/common-errors) if they fail validation.

In addition to common errors, `ReachPlanService` can return errors specific to
reach planning listed in [`ReachPlanError`](https://developers.google.com/google-ads/api/reference/rpc/v26/ReachPlanErrorEnum.ReachPlanError.md.txt).

## Code example

The curve includes impression and user reach in absolute numbers, as well as
universe sizes corresponding to the census population and YouTube audience size
for the requested demographic and country.


### Java

```java
private void getReachCurve(
    ReachPlanServiceClient reachPlanServiceClient, GenerateReachForecastRequest request) {
  GenerateReachForecastResponse response = reachPlanServiceClient.generateReachForecast(request);
  System.out.println("Reach curve output:");
  System.out.println(
      "Currency, Cost Micros, On-Target Reach, On-Target Imprs, Total Reach, Total Imprs,"
          + " Products");
  for (ReachForecast point : response.getReachCurve().getReachForecastsList()) {
    System.out.printf(
        "%s, \"",
        Joiner.on(", ")
            .join(
                request.getCurrencyCode(),
                String.valueOf(point.getCostMicros()),
                String.valueOf(point.getForecast().getOnTargetReach()),
                String.valueOf(point.getForecast().getOnTargetImpressions()),
                String.valueOf(point.getForecast().getTotalReach()),
                String.valueOf(point.getForecast().getTotalImpressions())));
    for (PlannedProductReachForecast product : point.getPlannedProductReachForecastsList()) {
      System.out.printf("[Product: %s, ", product.getPlannableProductCode());
      System.out.printf("Budget Micros: %s]", product.getCostMicros());
    }
    System.out.printf("\"%n");
  }
}
      
```

### C#

```c#
public void GetReachCurve(ReachPlanServiceClient reachPlanService,
    GenerateReachForecastRequest request)
{
    GenerateReachForecastResponse response = reachPlanService.GenerateReachForecast(
        request);
    Console.WriteLine("Reach curve output:");
    Console.WriteLine(
        "Currency, Cost Micros, On-Target Reach, On-Target Impressions, Total Reach," +
        " Total Impressions, Products");
    foreach (ReachForecast point in response.ReachCurve.ReachForecasts)
    {
        Console.Write($"{request.CurrencyCode}, ");
        Console.Write($"{point.CostMicros}, ");
        Console.Write($"{point.Forecast.OnTargetReach}, ");
        Console.Write($"{point.Forecast.OnTargetImpressions}, ");
        Console.Write($"{point.Forecast.TotalReach}, ");
        Console.Write($"{point.Forecast.TotalImpressions}, ");
        Console.Write("\"[");
        foreach (PlannedProductReachForecast productReachForecast in
            point.PlannedProductReachForecasts)
        {
            Console.Write($"(Product: {productReachForecast.PlannableProductCode}, ");
            Console.Write($"Budget Micros: {productReachForecast.CostMicros}), ");
        }

        Console.WriteLine("]\"");
    }
}
      
```

### PHP

```php
private static function getReachCurve(
    GoogleAdsClient $googleAdsClient,
    int $customerId,
    array $productMix,
    string $locationId,
    string $currencyCode
) {
    // Valid durations are between 1 and 90 days.
    $duration = new CampaignDuration(['duration_in_days' => 28]);
    $targeting = new Targeting([
        'plannable_location_id' => $locationId,
        'age_range' => ReachPlanAgeRange::AGE_RANGE_18_65_UP,
        'genders' => [
            new GenderInfo(['type' => GenderType::FEMALE]),
            new GenderInfo(['type' => GenderType::MALE])
        ],
        'devices' => [
            new DeviceInfo(['type' => Device::DESKTOP]),
            new DeviceInfo(['type' => Device::MOBILE]),
            new DeviceInfo(['type' => Device::TABLET])
        ]
    ]);

    // See the docs for defaults and valid ranges:
    // https://developers.google.com/google-ads/api/reference/rpc/latest/GenerateReachForecastRequest
    $response = $googleAdsClient->getReachPlanServiceClient()->generateReachForecast(
        GenerateReachForecastRequest::build($customerId, $duration, $productMix)
            ->setCurrencyCode($currencyCode)
            ->setTargeting($targeting)
    );

    printf(
        "Reach curve output:%sCurrency, Cost Micros, On-Target Reach, On-Target Imprs," .
            " Total Reach, Total Imprs, Products%s",
        PHP_EOL,
        PHP_EOL
    );
    foreach ($response->getReachCurve()->getReachForecasts() as $point) {
        $products = '';
        /** @var ReachForecast $point */
        foreach ($point->getPlannedProductReachForecasts() as $plannedProductReachForecast) {
            /** @var PlannedProductReachForecast $plannedProductReachForecast */
            $products .= sprintf(
                '(Product: %s, Budget Micros: %s)',
                $plannedProductReachForecast->getPlannableProductCode(),
                $plannedProductReachForecast->getCostMicros()
            );
        }
        printf(
            "%s, %d, %d, %d, %d, %d, %s%s",
            $currencyCode,
            $point->getCostMicros(),
            $point->getForecast()->getOnTargetReach(),
            $point->getForecast()->getOnTargetImpressions(),
            $point->getForecast()->getTotalReach(),
            $point->getForecast()->getTotalImpressions(),
            $products,
            PHP_EOL
        );
    }
}
      
```

### Python

```python
def request_reach_curve(
    client: GoogleAdsClient,
    customer_id: str,
    product_mix: list[PlannedProduct],
    location_id: str,
    currency_code: str,
):
    """Creates a sample request for a given product mix.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: The customer ID for the reach forecast.
        product_mix: The product mix for the reach forecast.
        location_id: The location ID to plan for.
        currency_code: Three-character ISO 4217 currency code.
    """
    # See the docs for defaults and valid ranges:
    # https://developers.google.com/google-ads/api/reference/rpc/latest/GenerateReachForecastRequest
    request: GenerateReachForecastRequest = client.get_type(
        "GenerateReachForecastRequest"
    )
    request.customer_id = customer_id
    # Valid durations are between 1 and 90 days.
    request.campaign_duration.duration_in_days = 28
    request.currency_code = currency_code
    request.cookie_frequency_cap = 0
    request.min_effective_frequency = 1
    request.planned_products = product_mix

    request.targeting.plannable_location_id = location_id
    request.targeting.age_range = (
        client.enums.ReachPlanAgeRangeEnum.AGE_RANGE_18_65_UP
    )

    # Add gender targeting to the request.
    gender_type: GenderTypeEnum
    for gender_type in [
        client.enums.GenderTypeEnum.FEMALE,
        client.enums.GenderTypeEnum.MALE,
    ]:
        gender: GenderInfo = client.get_type("GenderInfo")
        gender.type_ = gender_type
        request.targeting.genders.append(gender)

    # Add device targeting to the request.
    device_type: DeviceEnum
    for device_type in [
        client.enums.DeviceEnum.DESKTOP,
        client.enums.DeviceEnum.MOBILE,
        client.enums.DeviceEnum.TABLET,
    ]:
        device: DeviceInfo = client.get_type("DeviceInfo")
        device.type_ = device_type
        request.targeting.devices.append(device)

    reach_plan_service: ReachPlanServiceClient = client.get_service(
        "ReachPlanService"
    )
    response: GenerateReachForecastResponse = (
        reach_plan_service.generate_reach_forecast(request=request)
    )

    print(
        "Currency, Cost, On-Target Reach, On-Target Imprs, Total Reach,"
        " Total Imprs, Products"
    )
    point: ReachForecast
    for point in response.reach_curve.reach_forecasts:
        product_splits = []
        p: PlannedProductReachForecast
        for p in point.planned_product_reach_forecasts:
            product_splits.append(
                {p.plannable_product_code: p.cost_micros / ONE_MILLION}
            )
        print(
            [
                currency_code,
                point.cost_micros / ONE_MILLION,
                point.forecast.on_target_reach,
                point.forecast.on_target_impressions,
                point.forecast.total_reach,
                point.forecast.total_impressions,
                product_splits,
            ]
        )
      
```

### Ruby

```ruby
def get_reach_curve(
  client,
  reach_plan_service,
  customer_id,
  product_mix,
  location_id,
  currency_code)
  duration = client.resource.campaign_duration do |d|
    # Valid durations are between 1 and 90 days.
    d.duration_in_days = 28
  end

  targeting = client.resource.targeting do |t|
    t.plannable_location_id = location_id
    t.age_range = :AGE_RANGE_18_65_UP
    t.genders << client.resource.gender_info do |gender|
      gender.type = :FEMALE
    end
    t.genders << client.resource.gender_info do |gender|
      gender.type = :MALE
    end
    t.devices << client.resource.device_info do |device|
      device.type = :DESKTOP
    end
    t.devices << client.resource.device_info do |device|
      device.type = :MOBILE
    end
    t.devices << client.resource.device_info do |device|
      device.type = :TABLET
    end
  end

  # See the docs for defaults and valid ranges:
  # https://developers.google.com/google-ads/api/reference/rpc/latest/GenerateReachForecastRequest
  response = reach_plan_service.generate_reach_forecast(
    customer_id: customer_id,
    campaign_duration: duration,
    planned_products: product_mix,
    currency_code: currency_code,
    targeting: targeting,
  )

  puts "Reach curve output:"
  puts "Currency, Cost Micros, On-Target Reach, On-Target Imprs, " \
    "Total Reach, Total Imprs, Products"

  response.reach_curve.reach_forecasts.each do |point|
    products = ""
    point.planned_product_reach_forecasts.each do |product|
      products += "(Product: #{product.plannable_product_code}, "\
        "Cost Micros: #{product.cost_micros})"
    end
    puts "#{currency_code}, #{point.cost_micros}, " \
      "#{point.forecast.on_target_reach}, " \
      "#{point.forecast.on_target_impressions}, " \
      "#{point.forecast.total_reach}, " \
      "#{point.forecast.total_impressions}, " \
      "#{products}"
  end
end
      
```

### Perl

```perl
sub pull_reach_curve {
  my ($reach_plan_service, $reach_request) = @_;

  my $response = $reach_plan_service->generate_reach_forecast($reach_request);
  print "Reach curve output:\n";
  print "Currency,\tCost Micros,\tOn-Target Reach,\tOn-Target Imprs,\t" .
    "Total Reach,\tTotal Imprs,\tProducts\n";
  foreach my $point (@{$response->{reachCurve}{reachForecasts}}) {
    printf "%s,\t%d,\t%d,\t%d,\t%d,\t%d,\t'[", $reach_request->{currencyCode},
      $point->{costMicros}, $point->{forecast}{onTargetReach},
      $point->{forecast}{onTargetImpressions}, $point->{forecast}{totalReach},
      $point->{forecast}{totalImpressions};
    foreach my $productReachForecast (@{$point->{plannedProductReachForecasts}})
    {
      printf "(Product: %s, Budget Micros: %d), ",
        $productReachForecast->{plannableProductCode},
        $productReachForecast->{costMicros};
    }
    print "]'\n";
  }
}
      
```

### curl

> [!NOTE]
> **Note:** While a direct REST code sample for this step isn't provided here, you can achieve this using a manual REST request.   
>
> Refer to the Google Ads API REST interface documentation and the method-specific reference pages. You will need to construct the JSON payload based on the proto definitions.   
>
> Key Resources:
>
> - [Using REST](https://developers.google.com/google-ads/api/rest/overview.md.txt)
> - [REST Interface Structure](https://developers.google.com/google-ads/api/rest/design/overview.md.txt)
> - [JSON Field Mappings](https://developers.google.com/google-ads/api/rest/design/json-mappings.md.txt)
> - Consult the [REST API Reference](https://developers.google.com/google-ads/api/reference/rpc/latest/overview) for the specific service and method.

<br />

## Google Issue Tracker

Use Google Issue Tracker to ask support questions, report bugs, or submit
feature requests. Google tracks, prioritizes, and posts updates on your
requests directly in Issue Tracker. For more information, see [Create an issue
in Google Issue Tracker](https://developers.google.com/issue-tracker/guides/create-issue-ui).

Create an issue by using one of the following Issue Tracker templates:

- [Ask a general support question or submit documentation feedback](https://issuetracker.google.com/issues/new?component=2315943&template=2440256)
- [Report a technical bug](https://issuetracker.google.com/issues/new?component=2315943&template=2440158)
- [Submit an API feature request](https://issuetracker.google.com/issues/new?component=2315943&template=2440255)

Fill out all fields in the template with detailed information so that Google can
reproduce and prioritize your request. Google might close incomplete reports and
ask you to submit a new issue with the missing details.