Intra-campaign experiments are used to test a specific feature *within* a single
campaign. Unlike system-managed experiments where traffic is split between
control and treatment *campaigns* , intra-campaign experiments split traffic
*within* the campaign, based on whether the feature is enabled or not.

This workflow is supported for the following
[`ExperimentType`](https://developers.google.com/google-ads/api/reference/rpc/v26/ExperimentTypeEnum.ExperimentType) values:

`ADOPT_AI_MAX`
:   Tests the impact of AI Max features on Search campaigns.

`ADOPT_BROAD_MATCH_KEYWORDS`
:   Tests the impact of broad match keywords on Search campaigns.

`PMAX_TEXT_CUSTOMIZATION_FINAL_URL_EXPANSION`
:   Specifically used for testing Performance Max features: text customization
    and final URL expansion.
    Enables Google AI to send traffic to relevant landing pages and generate
    text assets to better match search queries.

## Setup

1. Define the `Experiment` (with `status` set to `ENABLED`), providing an experiment type, a control `ExperimentArm`, and a treatment `ExperimentArm`:
   - For `ADOPT_AI_MAX` and `ADOPT_BROAD_MATCH_KEYWORDS`, both the control arm and the treatment arm must reference the same campaign in `campaigns`.
   - For `PMAX_TEXT_CUSTOMIZATION_FINAL_URL_EXPANSION`, only the treatment arm specifies the Performance Max campaign in `campaigns` (leave `campaigns` unset on the control arm).
2. For `ADOPT_AI_MAX`, create a `CampaignOperation` update with a field mask to
   enable `ai_max_setting.enable_ai_max` and configure
   `asset_automation_settings`.

   > [!NOTE]
   > **Note:** A separate `CampaignOperation` update with a field mask is **not** required for `ADOPT_BROAD_MATCH_KEYWORDS` or `PMAX_TEXT_CUSTOMIZATION_FINAL_URL_EXPANSION`. Instead, the experimental settings are enabled automatically for the treatment group upon experiment creation.

3. Send a [`GoogleAdsService.Mutate`](https://developers.google.com/google-ads/api/reference/rpc/v26/GoogleAdsService/Mutate) request that includes
   mutate operations to create the experiment and experiment arms, and (for
   `ADOPT_AI_MAX`) to enable the test feature on the campaign.

For `ADOPT_AI_MAX` and `ADOPT_BROAD_MATCH_KEYWORDS`, traffic must be split
evenly (50/50) within the campaign so that 50% of traffic is exposed to the
enabled feature (the treatment group) and 50% is not (the control group).
`PMAX_TEXT_CUSTOMIZATION_FINAL_URL_EXPANSION` supports custom traffic splits
that sum to 100.


### Java

```java
// Create the experiment resource name using a temporary ID.
String experimentResourceName = ResourceNames.experiment(customerId, -1L);

// Create the experiment.
Experiment experiment =
    Experiment.newBuilder()
        .setResourceName(experimentResourceName)
        .setName("ADOPT_AI_MAX Experiment #" + UUID.randomUUID())
        .setType(ExperimentType.ADOPT_AI_MAX)
        .build();
MutateOperation experimentOperation =
    MutateOperation.newBuilder()
        .setExperimentOperation(ExperimentOperation.newBuilder().setCreate(experiment).build())
        .build();

// Create the control arm. Both arms in an intra-campaign experiment reference the same base
// campaign.
ExperimentArm controlArm =
    ExperimentArm.newBuilder()
        .setExperiment(experimentResourceName)
        .setName("Control Arm")
        .setControl(true)
        .setTrafficSplit(50)
        .addCampaigns(ResourceNames.campaign(customerId, campaignId))
        .build();
MutateOperation controlArmOperation =
    MutateOperation.newBuilder()
        .setExperimentArmOperation(
            ExperimentArmOperation.newBuilder().setCreate(controlArm).build())
        .build();

// Create the treatment arm.
ExperimentArm treatmentArm =
    ExperimentArm.newBuilder()
        .setExperiment(experimentResourceName)
        .setName("Treatment Arm")
        .setControl(false)
        .setTrafficSplit(50)
        .addCampaigns(ResourceNames.campaign(customerId, campaignId))
        .build();
MutateOperation treatmentArmOperation =
    MutateOperation.newBuilder()
        .setExperimentArmOperation(
            ExperimentArmOperation.newBuilder().setCreate(treatmentArm).build())
        .build();

// Create a campaign operation with an update mask to enable AI Max and configure asset
// automation settings.
// Note: For intra-campaign experiments, these settings are applied to the base campaign but are
// only active for the treatment traffic split.
Campaign campaign =
    Campaign.newBuilder()
        .setResourceName(ResourceNames.campaign(customerId, campaignId))
        .setAiMaxSetting(AiMaxSetting.newBuilder().setEnableAiMax(true).build())
        .addAssetAutomationSettings(
            AssetAutomationSetting.newBuilder()
                .setAssetAutomationType(AssetAutomationType.TEXT_ASSET_AUTOMATION)
                .setAssetAutomationStatus(AssetAutomationStatus.OPTED_IN)
                .build())
        .addAssetAutomationSettings(
            AssetAutomationSetting.newBuilder()
                .setAssetAutomationType(
                    AssetAutomationType.FINAL_URL_EXPANSION_TEXT_ASSET_AUTOMATION)
                .setAssetAutomationStatus(AssetAutomationStatus.OPTED_IN)
                .build())
        .build();

CampaignOperation campaignOp =
    CampaignOperation.newBuilder()
        .setUpdate(campaign)
        .setUpdateMask(FieldMasks.allSetFieldsOf(campaign))
        .build();
MutateOperation campaignMutateOperation =
    MutateOperation.newBuilder().setCampaignOperation(campaignOp).build();

// Send all mutate operations in a single Mutate request.
List<MutateOperation> mutateOperations =
    ImmutableList.of(
        experimentOperation,
        controlArmOperation,
        treatmentArmOperation,
        campaignMutateOperation);

try (GoogleAdsServiceClient googleAdsServiceClient =
    googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {

  MutateGoogleAdsRequest request =
      MutateGoogleAdsRequest.newBuilder()
          .setCustomerId(Long.toString(customerId))
          .addAllMutateOperations(mutateOperations)
          .build();

  MutateGoogleAdsResponse response = googleAdsServiceClient.mutate(request);
      
```

### C#

```c#
// Create the experiment resource name using a temporary ID.
string experimentResourceName = ResourceNames.Experiment(customerId, -1);

// Create the experiment.
MutateOperation experimentOperation = new MutateOperation()
{
    ExperimentOperation = new ExperimentOperation()
    {
        Create = new Experiment()
        {
            ResourceName = experimentResourceName,
            Name = $"ADOPT_AI_MAX Experiment #{ExampleUtilities.GetRandomString()}",
            Type = ExperimentType.AdoptAiMax
        }
    }
};

// Create the control arm. Both arms in an intra-campaign experiment
// reference the same base campaign.
MutateOperation controlArmOperation = new MutateOperation()
{
    ExperimentArmOperation = new ExperimentArmOperation()
    {
        Create = new ExperimentArm()
        {
            Experiment = experimentResourceName,
            Name = "Control Arm",
            Control = true,
            TrafficSplit = 50,
            Campaigns = { ResourceNames.Campaign(customerId, campaignId) }
        }
    }
};

// Create the treatment arm.
MutateOperation treatmentArmOperation = new MutateOperation()
{
    ExperimentArmOperation = new ExperimentArmOperation()
    {
        Create = new ExperimentArm()
        {
            Experiment = experimentResourceName,
            Name = "Treatment Arm",
            Control = false,
            TrafficSplit = 50,
            Campaigns = { ResourceNames.Campaign(customerId, campaignId) }
        }
    }
};

// Create a campaign operation with an update mask to enable AI Max and
// configure asset automation settings.
// Note: For intra-campaign experiments, these settings are applied to the
// base campaign but are only active for the treatment traffic split.
Campaign campaign = new Campaign()
{
    ResourceName = ResourceNames.Campaign(customerId, campaignId),
    AiMaxSetting = new Campaign.Types.AiMaxSetting { EnableAiMax = true }
};

campaign.AssetAutomationSettings.Add(new Campaign.Types.AssetAutomationSetting
{
    AssetAutomationType = AssetAutomationType.TextAssetAutomation,
    AssetAutomationStatus = AssetAutomationStatus.OptedIn
});

campaign.AssetAutomationSettings.Add(new Campaign.Types.AssetAutomationSetting
{
    AssetAutomationType = AssetAutomationType.FinalUrlExpansionTextAssetAutomation,
    AssetAutomationStatus = AssetAutomationStatus.OptedIn
});

MutateOperation campaignOperation = new MutateOperation()
{
    CampaignOperation = new CampaignOperation()
    {
        Update = campaign,
        UpdateMask = FieldMasks.AllSetFieldsOf(campaign)
    }
};

// Send all mutate operations in a single Mutate request.
List<MutateOperation> mutateOperations = new List<MutateOperation>
{
    experimentOperation,
    controlArmOperation,
    treatmentArmOperation,
    campaignOperation
};

MutateGoogleAdsResponse response = googleAdsService.Mutate(
    customerId.ToString(), mutateOperations);
      
```

### PHP

```
This example is not yet available in PHP; you can take a look at the other languages.
    
```

### Python

```python
# Create the experiment resource name using a temporary ID.
experiment_resource_name = googleads_service.experiment_path(
    customer_id, "-1"
)

# Create the experiment.
experiment_operation = client.get_type("MutateOperation")
experiment = experiment_operation.experiment_operation.create
experiment.resource_name = experiment_resource_name
experiment.name = f"ADOPT_AI_MAX Experiment #{uuid4()}"
experiment.type_ = client.enums.ExperimentTypeEnum.ADOPT_AI_MAX

# Create the control arm. Both arms in an intra-campaign experiment
# reference the same base campaign.
control_arm_operation = client.get_type("MutateOperation")
control_arm = control_arm_operation.experiment_arm_operation.create
control_arm.experiment = experiment_resource_name
control_arm.name = "Control Arm"
control_arm.control = True
control_arm.traffic_split = 50
control_arm.campaigns.append(
    googleads_service.campaign_path(customer_id, campaign_id)
)

# Create the treatment arm.
treatment_arm_operation = client.get_type("MutateOperation")
treatment_arm = treatment_arm_operation.experiment_arm_operation.create
treatment_arm.experiment = experiment_resource_name
treatment_arm.name = "Treatment Arm"
treatment_arm.control = False
treatment_arm.traffic_split = 50
treatment_arm.campaigns.append(
    googleads_service.campaign_path(customer_id, campaign_id)
)

# Create a campaign operation with an update mask to enable AI Max and
# configure asset automation settings.
# Note: For intra-campaign experiments, these settings are applied to the
# base campaign but are only active for the treatment traffic split.
campaign_operation = client.get_type("MutateOperation")
campaign = campaign_operation.campaign_operation.update
campaign.resource_name = googleads_service.campaign_path(
    customer_id, campaign_id
)
campaign.ai_max_setting.enable_ai_max = True

for asset_automation_type_enum in [
    client.enums.AssetAutomationTypeEnum.TEXT_ASSET_AUTOMATION,
    client.enums.AssetAutomationTypeEnum.FINAL_URL_EXPANSION_TEXT_ASSET_AUTOMATION,
]:
    asset_automation_setting = client.get_type(
        "Campaign"
    ).AssetAutomationSetting()
    asset_automation_setting.asset_automation_type = (
        asset_automation_type_enum
    )
    asset_automation_setting.asset_automation_status = (
        client.enums.AssetAutomationStatusEnum.OPTED_IN
    )
    campaign.asset_automation_settings.append(asset_automation_setting)

client.copy_from(
    campaign_operation.campaign_operation.update_mask,
    protobuf_helpers.field_mask(None, campaign._pb),
)

# Send all mutate operations in a single Mutate request.
mutate_operations = [
    experiment_operation,
    control_arm_operation,
    treatment_arm_operation,
    campaign_operation,
]

response = googleads_service.mutate(
    customer_id=customer_id,
    mutate_operations=mutate_operations,
)
      
```

### Ruby

```ruby
# Create the experiment resource name using a temporary ID.
experiment_resource_name = client.path.experiment(customer_id, "-1")

# Create the experiment.
experiment_operation = client.operation.mutate do |m|
  m.experiment_operation = client.operation.create_resource.experiment do |e|
    e.resource_name = experiment_resource_name
    e.name = "ADOPT_AI_MAX Experiment #{(Time.new.to_f * 1000).to_i}"
    e.type = :ADOPT_AI_MAX
  end
end

# Create the control arm. Both arms in an intra-campaign experiment
# reference the same base campaign.
campaign_resource_name = client.path.campaign(customer_id, campaign_id)
control_arm_operation = client.operation.mutate do |m|
  m.experiment_arm_operation = client.operation.create_resource.experiment_arm do |arm|
    arm.experiment = experiment_resource_name
    arm.name = "Control Arm"
    arm.control = true
    arm.traffic_split = 50
    arm.campaigns << campaign_resource_name
  end
end

# Create the treatment arm.
treatment_arm_operation = client.operation.mutate do |m|
  m.experiment_arm_operation = client.operation.create_resource.experiment_arm do |arm|
    arm.experiment = experiment_resource_name
    arm.name = "Treatment Arm"
    arm.control = false
    arm.traffic_split = 50
    arm.campaigns << campaign_resource_name
  end
end

# Create a campaign operation with an update mask to enable AI Max and
# configure asset automation settings.
# Note: For intra-campaign experiments, these settings are applied to the
# base campaign but are only active for the treatment traffic split.
campaign_operation = client.operation.mutate do |m|
  m.campaign_operation = client.operation.update_resource.campaign(campaign_resource_name) do |c|
    c.ai_max_setting = client.resource.ai_max_setting do |aims|
      aims.enable_ai_max = true
    end

    [:TEXT_ASSET_AUTOMATION, :FINAL_URL_EXPANSION_TEXT_ASSET_AUTOMATION].each do |type|
      c.asset_automation_settings << client.resource.asset_automation_setting do |setting|
        setting.asset_automation_type = type
        setting.asset_automation_status = :OPTED_IN
      end
    end
  end
end

# Send all mutate operations in a single Mutate request.
mutate_operations = [
  experiment_operation,
  control_arm_operation,
  treatment_arm_operation,
  campaign_operation,
]

response = client.service.google_ads.mutate(
  customer_id: customer_id,
  mutate_operations: mutate_operations,
)
      
```

### Perl

```
This example is not yet available in Perl; you can take a look at the other languages.
    
```

### 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)
> - [REST Interface Structure](https://developers.google.com/google-ads/api/rest/design/overview)
> - [JSON Field Mappings](https://developers.google.com/google-ads/api/rest/design/json-mappings)
> - Consult the [REST API Reference](https://developers.google.com/google-ads/api/reference/rpc/latest/overview) for the specific service and method.

<br />

## Report on the experiment

Because control and treatment traffic are mixed within a single campaign, you
must use [direct experiment reporting](https://developers.google.com/google-ads/api/docs/experiments/reporting) to compare metrics between the
control and treatment groups. Standard campaign-level reporting only shows
aggregated metrics for the entire campaign and cannot distinguish between the
two groups.

The following GAQL query retrieves click statistics for an `ADOPT_AI_MAX`
intra-campaign experiment over the last 30 days:

    SELECT
      experiment.resource_name,
      experiment.name,
      metrics.clicks,
      metrics.control_clicks,
      metrics.clicks_point_estimate,
      metrics.clicks_p_value
    FROM experiment
    WHERE segments.date DURING LAST_30_DAYS
      AND experiment.type = 'ADOPT_AI_MAX'

## Promote, graduate, or end the experiment

After evaluating the results, you can complete the experiment using
[`ExperimentService`](https://developers.google.com/google-ads/api/reference/rpc/v26/ExperimentService):

- **End** : If you are not satisfied with the results, use [`EndExperiment`](https://developers.google.com/google-ads/api/reference/rpc/v26/ExperimentService/EndExperiment). The feature is disabled, and the campaign reverts to serving all traffic without the experimental feature. This is a synchronous operation.
- **Promote** (`ADOPT_AI_MAX` and `ADOPT_BROAD_MATCH_KEYWORDS`): If you are satisfied with the results of a Search intra-campaign experiment, use [`PromoteExperiment`](https://developers.google.com/google-ads/api/reference/rpc/v26/ExperimentService/PromoteExperiment). This applies the experimental change as the new permanent state of the campaign. This is an asynchronous operation; see [Asynchronous errors](https://developers.google.com/google-ads/api/docs/experiments/async-errors) for details.
- **Graduate** (`PMAX_TEXT_CUSTOMIZATION_FINAL_URL_EXPANSION`): `PromoteExperiment` is not supported for `PMAX_TEXT_CUSTOMIZATION_FINAL_URL_EXPANSION`; instead, use [`GraduateExperiment`](https://developers.google.com/google-ads/api/reference/rpc/v26/ExperimentService/GraduateExperiment) to apply the treatment arm's text customization and final URL expansion settings in-place to the existing Performance Max campaign. Include a [`CampaignBudgetMapping`](https://developers.google.com/google-ads/api/reference/rpc/v26/CampaignBudgetMapping) entry in `campaign_budget_mappings` specifying the treatment arm's campaign (`experiment_campaign`) and its budget (`campaign_budget`). This is a synchronous operation. (`GraduateExperiment` is not supported for `ADOPT_AI_MAX` or `ADOPT_BROAD_MATCH_KEYWORDS` because there is no separate treatment campaign to graduate.)