Create a Customer Match audience by sending a
[`CreateUserListRequest`](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists.md.txt).

> [!NOTE]
> **Note:** Operations that create, update, or delete user lists must comply with the [EU Political Ads Regulation (EU PAR) policy](https://developers.google.com/google-ads/api/docs/api-policy/eu-par.md.txt) and may return an `EU_POLITICAL_ADVERTISING_DECLARATION_REQUIRED` error if the account lacks the required declaration.

## Construct the request body

Create a [`UserList`](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists.md.txt#UserList)
resource. Here are the common fields to set.

`displayName`
:   **Required** The display name of the list. The name must be unique among all
    of the lists owned by the account.

`description`
:   A brief description of the list.

`integrationCode`
:   An ID from your system.

`membershipDuration`

:   The duration an audience member remains in the list after added. The value
    must correspond to whole days. If not set, defaults to the maximum.

    If using JSON, calculate the number of seconds by multiplying the duration
    days by `86400` (the number of seconds per day). Then set the value to the
    multiplication result, followed by `s`. For example, if you want a
    membership duration of 90 days, use the value `7776000s` since `90 * 86400 =
    7776000`.

    If using the protocol buffer format, use the convenience method for
    constructing a
    [`Duration`](https://protobuf.dev/reference/protobuf/google.protobuf/#duration)
    object based on the number of days, if such a convenience method exists. For
    example, the `protobuf-java-util` library for Java has a
    [`fromDays()`](https://protobuf.dev/reference/java/api-docs/com/google/protobuf/util/Durations.html#fromDays-long-)
    convenience method. Otherwise, calculate the number of seconds by
    multiplying the duration days by `86400` (the number of seconds per day),
    and use the result to set the `seconds` field of the `Duration` object.

### Required fields for Customer Match


The `ingestedUserListInfo` field is required for a Customer Match audience.

Here are the fields to set on the
[`IngestedUserListInfo`](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists.md.txt#ingesteduserlistinfo)
based on the type of data you want to use to add audience members to the list.

#### Contact information list

Set the following fields if you want to use contact information (hashed email
addresses, phone numbers, or addresses) to define your audience members:

`uploadKeyTypes`

:   Set to a list containing the `CONTACT_ID` upload key type.

    The `uploadKeyTypes` list must contain only *one* entry.

`contactIdInfo`

:   Set to a
    [`ContactIdInfo`](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists.md.txt#contactidinfo)
    message with the `dataSourceType` set to the appropriate value from the
    [`DataSourceType`](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists.md.txt#datasourcetype)
    enum.

#### Mobile ID list

Set the following fields if you want to use mobile IDs to define your audience
members:

`uploadKeyTypes`

:   Set to a list containing the `MOBILE_ID` upload key type.

    The `uploadKeyTypes` list must contain only *one* entry.

`mobileIdInfo`

:   Set to a
    [`MobileIdInfo`](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists.md.txt#mobileidinfo)
    message:

    - Set the `dataSourceType` to the appropriate value from the
      [`DataSourceType`](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists.md.txt#datasourcetype)
      enum.

    - Set the `keySpace` to `IOS` or `ANDROID`.

    - Set the `appId` to the string that uniquely identifies the mobile
      application from which the data was collected.

      - For iOS, the ID string is the 9 digit string that appears at the end
        of the App Store URL. For example, `476943146` for the "Flood-It!"
        app with App Store link
        <https://apps.apple.com/us/app/flood-it/id476943146>.

      - For Android, the ID string is the application's package name. For
        example, `com.labpixies.flood` for the "Flood-It!" app with
        Google Play link
        <https://play.google.com/store/apps/details?id=com.labpixies.flood>.

#### User ID list

Set the following fields if you want to use user IDs to define your audience
members:

`uploadKeyTypes`

:   Set to a list containing the `USER_ID` upload key type.

    The `uploadKeyTypes` list must contain only *one* entry.

<br />

## Construct the request

To create a Customer Match audience, construct a
[`create` request](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists.md.txt) for the
`UserList` resource.

1. Set the `parent` field to the resource name of the account. The resource
   name must follow the format
   `accountTypes/{accountType}/accounts/{account}`.

   <br />

2. Use the `UserList` you constructed for the body of the request.

3. If your credentials aren't for a Google Account that's a user on the
   `parent`, set headers as described in [Configure destinations and
   headers](https://developers.google.com/data-manager/api/devguides/concepts/destinations#when-to-use).


Here are sample requests to create a Customer Match audience with a
`membershipDuration` of 30 days (`2592000s`). Select the **REST** tab and click
**Open in API Explorer** to try a request from your browser, or select the tab
for your chosen programming language if you
[installed a client library](https://developers.google.com/data-manager/api/devguides/quickstart/install-library.md.txt).

### REST

### Contact information


[Open in API Explorer](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists/create.md.txt?apix=true&apix_params=%0A%7B%22resource%22:%7B%22description%22:%22Customer+Match+for+contact+info%22,%22displayName%22:%22Contact+info+audience%22,%22ingestedUserListInfo%22:%7B%22contactIdInfo%22:%7B%22dataSourceType%22:%22DATA_SOURCE_TYPE_FIRST_PARTY%22%7D,%22uploadKeyTypes%22:%5B%22CONTACT_ID%22%5D%7D,%22membershipDuration%22:%222592000s%22%7D%7D "Open this sample in API Explorer")

```json
{
    "description": "Customer Match for contact info",
    "displayName": "Contact info audience",
    "ingestedUserListInfo": {
        "contactIdInfo": {
            "dataSourceType": "DATA_SOURCE_TYPE_FIRST_PARTY"
        },
        "uploadKeyTypes": [
            "CONTACT_ID"
        ]
    },
    "membershipDuration": "2592000s"
}
```

<br />

### Mobile IDs


[Open in API Explorer](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists/create.md.txt?apix=true&apix_params=%0A%7B%22resource%22:%7B%22description%22:%22Customer+Match+for+mobile+IDs%22,%22displayName%22:%22Mobile+ID+audience%22,%22ingestedUserListInfo%22:%7B%22mobileIdInfo%22:%7B%22dataSourceType%22:%22DATA_SOURCE_TYPE_FIRST_PARTY%22,%22keySpace%22:%22ANDROID%22,%22appId%22:%22com.labpixies.flood%22%7D,%22uploadKeyTypes%22:%5B%22MOBILE_ID%22%5D%7D,%22membershipDuration%22:%222592000s%22%7D%7D "Open this sample in API Explorer")

```json
{
    "description": "Customer Match for mobile IDs",
    "displayName": "Mobile ID audience",
    "ingestedUserListInfo": {
        "mobileIdInfo": {
            "dataSourceType": "DATA_SOURCE_TYPE_FIRST_PARTY",
            "keySpace": "ANDROID",
            "appId": "com.labpixies.flood"
        },
        "uploadKeyTypes": [
            "MOBILE_ID"
        ]
    },
    "membershipDuration": "2592000s"
}
```

### User IDs


[Open in API Explorer](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists/create.md.txt?apix=true&apix_params=%0A%7B%22resource%22:%7B%22description%22:%22Customer+Match+for+user+IDs%22,%22displayName%22:%22User+ID+audience%22,%22ingestedUserListInfo%22:%7B%22userIdInfo%22:%7B%22dataSourceType%22:%22DATA_SOURCE_TYPE_FIRST_PARTY%22%7D,%22uploadKeyTypes%22:%5B%22USER_ID%22%5D%7D,%22membershipDuration%22:%222592000s%22%7D%7D "Open this sample in API Explorer")

```json
{
    "description": "Customer Match for user IDs",
    "displayName": "User ID audience",
    "ingestedUserListInfo": {
        "userIdInfo": {
            "dataSourceType": "DATA_SOURCE_TYPE_FIRST_PARTY"
        },
        "uploadKeyTypes": [
            "USER_ID"
        ]
    },
    "membershipDuration": "2592000s"
}
```

### .NET

```
// Runs the example.
private void RunExample(
    AccountType operatingAccountType,
    string operatingAccountId,
    bool validateOnly,
    UploadKeyType uploadKeyType = UploadKeyType.ContactId,
    AccountType? loginAccountType = null,
    string? loginAccountId = null,
    AccountType? linkedAccountType = null,
    string? linkedAccountId = null,
    KeySpace? keySpace = null,
    string? appId = null
)
{
    if (loginAccountId == null ^ loginAccountType == null)
    {
        throw new ArgumentException(
            "Must specify either both or neither of login account ID and login account "
                + "type"
        );
    }
    if (linkedAccountId == null ^ linkedAccountType == null)
    {
        throw new ArgumentException(
            "Must specify either both or neither of linked account ID and linked account "
                + "type"
        );
    }
    if (uploadKeyType == UploadKeyType.MobileId)
    {
        if (!keySpace.HasValue || string.IsNullOrEmpty(appId))
        {
            throw new ArgumentException(
                "Must specify both keySpace and appId when uploadKeyType is MobileId"
            );
        }
    }
    else if (keySpace.HasValue || !string.IsNullOrEmpty(appId))
    {
        throw new ArgumentException(
            "keySpace and appId are only supported when uploadKeyType is MobileId"
        );
    }

    // Optional: Sets headers to specify the login-account and linked-account if your
    // credentials aren't for a user in the operating account (for example, when
    // authenticating through a Google Ads manager account or Data Partner account).
    CallSettings? callSettings = null;
    if (loginAccountType.HasValue && loginAccountId != null)
    {
        callSettings = callSettings.WithHeader(
            "login-account",
            // Uses the AccountName utility to construct the login-account resource name.
            AccountName.Format(GetEnumOriginalName(loginAccountType.Value), loginAccountId)
        );
    }
    if (linkedAccountType.HasValue && linkedAccountId != null)
    {
        callSettings = callSettings.WithHeader(
            "linked-account",
            // Uses the AccountName utility to construct the linked-account resource name.
            AccountName.Format(
                GetEnumOriginalName(linkedAccountType.Value),
                linkedAccountId
            )
        );
    }

    // Configures the Customer Match user list.
    // For more details on constructing a UserList, see:
    // https://developers.google.com/data-manager/api/devguides/audiences/google-ads/customer-match/create-audience
    var ingestedUserListInfo = new IngestedUserListInfo
    {
        // Sets to a list containing a single UploadKeyType.
        UploadKeyTypes = { uploadKeyType },
    };

    switch (uploadKeyType)
    {
        case UploadKeyType.ContactId:
            // Use CONTACT_ID for hashed email addresses, phone numbers, or physical
            // addresses.
            ingestedUserListInfo.ContactIdInfo = new ContactIdInfo
            {
                DataSourceType = DataSourceType.FirstParty,
            };
            break;
        case UploadKeyType.UserId:
            // Use USER_ID for first-party user IDs assigned to logged-in users.
            // Note: Display & Video 360 does not support USER_ID lists.
            ingestedUserListInfo.UserIdInfo = new UserIdInfo
            {
                DataSourceType = DataSourceType.FirstParty,
            };
            break;
        case UploadKeyType.MobileId:
            // Use MOBILE_ID for mobile advertising IDs.
            ingestedUserListInfo.MobileIdInfo = new MobileIdInfo
            {
                DataSourceType = DataSourceType.FirstParty,
                KeySpace = keySpace!.Value,
                AppId = appId,
            };
            break;
        default:
            throw new ArgumentException($"Unsupported upload key type: {uploadKeyType}");
    }

    string uploadKeyTypeName = GetEnumOriginalName(uploadKeyType);
    var userList = new UserList
    {
        // Required. The display name must be unique among all lists owned by the account.
        DisplayName =
            $"Customer Match ({uploadKeyTypeName}) - {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
        Description = $"Customer Match audience for {uploadKeyTypeName}",
        // Optional. The duration an audience member remains in the list after being added.
        // Must correspond to whole days (defaults to maximum if not set).
        MembershipDuration = Duration.FromTimeSpan(TimeSpan.FromDays(30)),
        // Required for a Customer Match audience.
        IngestedUserListInfo = ingestedUserListInfo,
    };

    // Creates a client for UserListService.
    UserListServiceClient userListServiceClient = UserListServiceClient.Create();

    // Builds the request targeting the parent operating account.
    var request = new CreateUserListRequest
    {
        Parent = AccountName.Format(
            GetEnumOriginalName(operatingAccountType),
            operatingAccountId
        ),
        UserList = userList,
        // Sets validate_only. If true, then the Data Manager API only validates the
        // request but doesn't apply changes.
        ValidateOnly = validateOnly,
    };

    // Sends the request with any configured header settings.
    UserList response = userListServiceClient.CreateUserList(request, callSettings);

    // Logs the created user list. When validate_only is false, response.Id contains the
    // created user list ID, which you pass as the ProductDestinationId in subsequent
    // ingest or remove requests.
    Console.WriteLine($"Response:\n{response}");
}

// Returns the original proto enum name (for example, "GOOGLE_ADS" for
// AccountType.GoogleAds).
private static string GetEnumOriginalName<T>(T enumValue)
    where T : struct, System.Enum =>
    typeof(T)
        .GetField(enumValue.ToString())
        ?.GetCustomAttribute<OriginalNameAttribute>()
        ?.Name
    ?? enumValue.ToString();
```

### Java

```java
/**
 * Runs the example.
 *
 * @param params the parameters for the example
 * @throws IOException if the API request fails
 */
private void runExample(ParamsConfig params) throws IOException {
  // Optional: Sets headers to specify the login-account and linked-account if your credentials
  // aren't for a user in the operating account (for example, when authenticating through a Google
  // Ads manager account or Data Partner account).
  Map<String, String> headers = new HashMap<>();
  if (params.loginAccountId != null) {
    headers.put(
        "login-account",
        // Uses the AccountName utility to construct the login-account resource name.
        AccountName.format(params.loginAccountType.name(), params.loginAccountId));
  }
  if (params.linkedAccountId != null) {
    headers.put(
        "linked-account",
        // Uses the AccountName utility to construct the linked-account resource name.
        AccountName.format(params.linkedAccountType.name(), params.linkedAccountId));
  }

  // Configures the Customer Match user list.
  // For more details on constructing a UserList, see:
  // https://developers.google.com/data-manager/api/devguides/audiences/google-ads/customer-match/create-audience
  IngestedUserListInfo.Builder ingestedUserListInfoBuilder =
      IngestedUserListInfo.newBuilder()
          // Sets to a single UploadKeyType.
          .addUploadKeyTypes(params.uploadKeyType);

  switch (params.uploadKeyType) {
    case CONTACT_ID:
      // Use CONTACT_ID for hashed email addresses, phone numbers, or physical addresses.
      ingestedUserListInfoBuilder.setContactIdInfo(
          ContactIdInfo.newBuilder()
              .setDataSourceType(DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY));
      break;
    case USER_ID:
      // Use USER_ID for first-party user IDs assigned to logged-in users.
      // Note: Display & Video 360 does not support USER_ID lists.
      ingestedUserListInfoBuilder.setUserIdInfo(
          UserIdInfo.newBuilder().setDataSourceType(DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY));
      break;
    case MOBILE_ID:
      // Use MOBILE_ID for mobile advertising IDs.
      ingestedUserListInfoBuilder.setMobileIdInfo(
          MobileIdInfo.newBuilder()
              .setDataSourceType(DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY)
              .setKeySpace(params.keySpace)
              .setAppId(params.appId));
      break;
    default:
      throw new IllegalArgumentException("Unsupported upload key type: " + params.uploadKeyType);
  }

  UserList userList =
      UserList.newBuilder()
          // Required. The display name must be unique among all lists owned by the account.
          .setDisplayName(
              String.format(
                  "Customer Match (%s) - %s",
                  params.uploadKeyType.name(),
                  LocalDateTime.now(ZoneId.systemDefault())
                      .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))))
          .setDescription(
              String.format("Customer Match audience for %s", params.uploadKeyType.name()))
          // Optional. The duration an audience member remains in the list after being added. Must
          // correspond to whole days (defaults to maximum if not set).
          .setMembershipDuration(Durations.fromDays(30))
          // Required for a Customer Match audience.
          .setIngestedUserListInfo(ingestedUserListInfoBuilder)
          .build();

  // Configures the settings for UserListService to include headers.
  UserListServiceSettings userListServiceSettings =
      UserListServiceSettings.newBuilder()
          .setHeaderProvider(FixedHeaderProvider.create(headers))
          .build();

  // Creates a client for UserListService using the settings to include headers if provided.
  // Uses a try-with-resources block so the client shuts down properly.
  try (UserListServiceClient userListServiceClient =
      UserListServiceClient.create(userListServiceSettings)) {
    // Builds the request targeting the parent operating account.
    CreateUserListRequest request =
        CreateUserListRequest.newBuilder()
            .setParent(
                AccountName.format(params.operatingAccountType.name(), params.operatingAccountId))
            .setUserList(userList)
            // Sets validate_only. If true, then the Data Manager API only validates the request
            // but doesn't apply changes.
            .setValidateOnly(params.validateOnly)
            .build();

    // Sends the request.
    UserList response = userListServiceClient.createUserList(request);

    // Logs the created user list. When validate_only is false, response.getId() contains the
    // created user list ID, which you pass as the product_destination_id in subsequent ingest or
    // remove requests.
    LOGGER.info(() -> String.format("Response:%n%s", response));
  }
}
```

### Node

```javascript
/**
 * Runs the CreateUserList sample.
 * @param {string} operatingAccountType The account type of the operating account.
 * @param {string} operatingAccountId The ID of the operating account.
 * @param {boolean} validateOnly Whether to enable validate_only on the request.
 * @param {protos.google.ads.datamanager.v1.IngestedUserListInfo.UploadKeyType} uploadKeyType The upload key type of the user list.
 * @param {string} [loginAccountType] The account type of the login account.
 * @param {string} [loginAccountId] The ID of the login account.
 * @param {string} [linkedAccountType] The account type of the linked account.
 * @param {string} [linkedAccountId] The ID of the linked account.
 * @param {protos.google.ads.datamanager.v1.MobileIdInfo.KeySpace} [keySpace] The key space of mobile IDs. Required when uploadKeyType is MOBILE_ID.
 * @param {string} [appId] The mobile application ID. Required when uploadKeyType is MOBILE_ID.
 */
async function runSample(
  operatingAccountType: string,
  operatingAccountId: string,
  validateOnly: boolean,
  uploadKeyType: protos.google.ads.datamanager.v1.IngestedUserListInfo.UploadKeyType = CONTACT_ID,
  loginAccountType?: string,
  loginAccountId?: string,
  linkedAccountType?: string,
  linkedAccountId?: string,
  keySpace?: protos.google.ads.datamanager.v1.MobileIdInfo.KeySpace,
  appId?: string,
) {
  // Creates a client for UserListService.
  const userListClient = new UserListServiceClient();

  // Optional: Sets headers to specify the login-account and linked-account if
  // your credentials aren't for a user in the operating account (for example,
  // when authenticating through a Google Ads manager account or Data Partner
  // account).
  const headers: {[key: string]: string} = {};

  if (loginAccountType && loginAccountId) {
    headers['login-account'] = userListClient.accountPath(
      loginAccountType,
      loginAccountId,
    );
  }

  if (linkedAccountType && linkedAccountId) {
    headers['linked-account'] = userListClient.accountPath(
      linkedAccountType,
      linkedAccountId,
    );
  }

  // Configures the Customer Match user list.
  // For more details on constructing a UserList, see:
  // https://developers.google.com/data-manager/api/devguides/audiences/google-ads/customer-match/create-audience
  const ingestedUserListInfo = IngestedUserListInfo.create({
    // Sets to a list containing a single UploadKeyType.
    uploadKeyTypes: [uploadKeyType],
  });

  switch (uploadKeyType) {
    case CONTACT_ID:
      // Use CONTACT_ID for hashed email addresses, phone numbers, or physical
      // addresses.
      ingestedUserListInfo.contactIdInfo = ContactIdInfo.create({
        dataSourceType: DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY,
      });
      break;
    case USER_ID:
      // Use USER_ID for first-party user IDs assigned to logged-in users.
      // Note: Display & Video 360 does not support USER_ID lists.
      ingestedUserListInfo.userIdInfo = UserIdInfo.create({
        dataSourceType: DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY,
      });
      break;
    case MOBILE_ID:
      // Use MOBILE_ID for mobile advertising IDs.
      ingestedUserListInfo.mobileIdInfo = MobileIdInfo.create({
        dataSourceType: DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY,
        keySpace,
        appId,
      });
      break;
    default:
      throw new Error(`Unsupported upload key type: ${uploadKeyType}`);
  }

  const uploadKeyTypeName = Object.keys(
    IngestedUserListInfo.UploadKeyType,
  ).find(
    key =>
      IngestedUserListInfo.UploadKeyType[
        key as keyof typeof IngestedUserListInfo.UploadKeyType
      ] === uploadKeyType,
  );
  const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);

  const userList = UserList.create({
    // Required. The display name must be unique among all lists owned by the
    // account.
    displayName: `Customer Match (${uploadKeyTypeName}) - ${timestamp}`,
    description: `Customer Match audience for ${uploadKeyTypeName}`,
    // Optional. The duration an audience member remains in the list after
    // being added. Must correspond to whole days (defaults to maximum if
    // not set).
    membershipDuration: Duration.create({
      seconds: 30 * 24 * 60 * 60,
    }),
    // Required for a Customer Match audience.
    ingestedUserListInfo,
  });

  // Builds the request targeting the parent operating account.
  const request = CreateUserListRequest.create({
    parent: userListClient.accountPath(
      operatingAccountType,
      operatingAccountId,
    ),
    userList,
    // Sets validate_only. If true, then the Data Manager API only
    // validates the request but doesn't apply changes.
    validateOnly,
  });

  // Sends the request with any configured header metadata.
  const [response] = await userListClient.createUserList(request, {
    otherArgs: {headers},
  });

  // Logs the created user list. When validate_only is false, response.id
  // contains the created user list ID, which you pass as the
  // product_destination_id in subsequent ingest or remove requests.
  console.log('Response:\n', response);
}
```

### PHP

```php
/**
 * Runs the sample.
 *
 * @param int $operatingAccountType The account type of the operating account.
 * @param string $operatingAccountId The ID of the operating account.
 * @param bool $validateOnly Whether to enable validateOnly on the request.
 * @param int $uploadKeyType The upload key type of the user list.
 * @param int|null $loginAccountType The account type of the login account.
 * @param string|null $loginAccountId The ID of the login account.
 * @param int|null $linkedAccountType The account type of the linked account.
 * @param string|null $linkedAccountId The ID of the linked account.
 * @param int|null $keySpace The key space of mobile IDs. Required when uploadKeyType is MOBILE_ID.
 * @param string|null $appId The mobile application ID. Required when uploadKeyType is MOBILE_ID.
 */
function main(
    int $operatingAccountType,
    string $operatingAccountId,
    bool $validateOnly,
    int $uploadKeyType = UploadKeyType::CONTACT_ID,
    ?int $loginAccountType = null,
    ?string $loginAccountId = null,
    ?int $linkedAccountType = null,
    ?string $linkedAccountId = null,
    ?int $keySpace = null,
    ?string $appId = null
): void {
    // Validates parameters.
    if (($loginAccountType !== null) !== ($loginAccountId !== null)) {
        throw new \InvalidArgumentException(
            'Must specify either both or neither of login account type and login account ID'
        );
    }
    if (($linkedAccountType !== null) !== ($linkedAccountId !== null)) {
        throw new \InvalidArgumentException(
            'Must specify either both or neither of linked account type and linked account ID'
        );
    }
    if ($uploadKeyType === UploadKeyType::MOBILE_ID) {
        if ($keySpace === null || empty($appId)) {
            throw new \InvalidArgumentException(
                'Must specify both key_space and app_id when upload_key_type is MOBILE_ID'
            );
        }
    } elseif ($keySpace !== null || !empty($appId)) {
        throw new \InvalidArgumentException(
            'key_space and app_id are only supported when upload_key_type is MOBILE_ID'
        );
    }

    // Optional: Sets headers to specify the login-account and linked-account if
    // your credentials aren't for a user in the operating account (for example,
    // when authenticating through a Google Ads manager account or Data Partner
    // account).
    $headers = [];

    if ($loginAccountType !== null && $loginAccountId !== null) {
        $headers['login-account'] = [
            UserListServiceClient::accountName(
                AccountType::name($loginAccountType),
                $loginAccountId
            ),
        ];
    }

    if ($linkedAccountType !== null && $linkedAccountId !== null) {
        $headers['linked-account'] = [
            UserListServiceClient::accountName(
                AccountType::name($linkedAccountType),
                $linkedAccountId
            ),
        ];
    }

    // Configures the Customer Match user list.
    // For more details on constructing a UserList, see:
    // https://developers.google.com/data-manager/api/devguides/audiences/google-ads/customer-match/create-audience
    $ingestedUserListInfo = (new IngestedUserListInfo())
        // Sets to a list containing a single UploadKeyType.
        ->setUploadKeyTypes([$uploadKeyType]);

    switch ($uploadKeyType) {
        case UploadKeyType::CONTACT_ID:
            // Use CONTACT_ID for hashed email addresses, phone numbers, or physical addresses.
            $ingestedUserListInfo->setContactIdInfo(
                (new ContactIdInfo())
                    ->setDataSourceType(DataSourceType::DATA_SOURCE_TYPE_FIRST_PARTY)
            );
            break;
        case UploadKeyType::USER_ID:
            // Use USER_ID for first-party user IDs assigned to logged-in users.
            // Note: Display & Video 360 does not support USER_ID lists.
            $ingestedUserListInfo->setUserIdInfo(
                (new UserIdInfo())
                    ->setDataSourceType(DataSourceType::DATA_SOURCE_TYPE_FIRST_PARTY)
            );
            break;
        case UploadKeyType::MOBILE_ID:
            // Use MOBILE_ID for mobile advertising IDs.
            $ingestedUserListInfo->setMobileIdInfo(
                (new MobileIdInfo())
                    ->setDataSourceType(DataSourceType::DATA_SOURCE_TYPE_FIRST_PARTY)
                    ->setKeySpace($keySpace)
                    ->setAppId($appId)
            );
            break;
        default:
            throw new \InvalidArgumentException(
                sprintf('Unsupported upload key type: %s', UploadKeyType::name($uploadKeyType))
            );
    }

    $uploadKeyTypeName = UploadKeyType::name($uploadKeyType);
    $userList = (new UserList())
        // Required. The display name must be unique among all lists owned by the account.
        ->setDisplayName(sprintf(
            'Customer Match (%s) - %s',
            $uploadKeyTypeName,
            (new \DateTime())->format('Y-m-d H:i:s')
        ))
        ->setDescription(sprintf('Customer Match audience for %s', $uploadKeyTypeName))
        // Optional. The duration an audience member remains in the list after being added. Must
        // correspond to whole days (defaults to maximum if not set).
        ->setMembershipDuration((new Duration())->setSeconds(30 * 24 * 60 * 60))
        // Required for a Customer Match audience.
        ->setIngestedUserListInfo($ingestedUserListInfo);

    // Creates a client for UserListService.
    $userListClient = new UserListServiceClient();

    // Builds the request targeting the parent operating account.
    $request = (new CreateUserListRequest())
        ->setParent(
            UserListServiceClient::accountName(
                AccountType::name($operatingAccountType),
                $operatingAccountId
            )
        )
        ->setUserList($userList)
        // Sets validate_only. If true, then the Data Manager API only validates the request
        // but doesn't apply changes.
        ->setValidateOnly($validateOnly);

    try {
        // Sends the request with any configured header metadata.
        $response = $userListClient->createUserList($request, ['headers' => $headers]);

        // Logs the created user list. When validate_only is false, $response->getId() contains the
        // created user list ID, which you pass as the product_destination_id in subsequent ingest or
        // remove requests.
        echo "Response:\n" . json_encode(json_decode($response->serializeToJsonString()), JSON_PRETTY_PRINT) . "\n";
    } catch (ApiException $e) {
        echo 'Error sending request: ' . $e->getMessage() . "\n";
    } finally {
        $userListClient->close();
    }
}
```

### Python

```python
def main(
    operating_account_type: datamanager_v1.ProductAccount.AccountType,
    operating_account_id: str,
    validate_only: bool,
    upload_key_type: datamanager_v1.IngestedUserListInfo.UploadKeyType = (
        datamanager_v1.IngestedUserListInfo.UploadKeyType.CONTACT_ID
    ),
    login_account_type: Optional[
        datamanager_v1.ProductAccount.AccountType
    ] = None,
    login_account_id: Optional[str] = None,
    linked_account_type: Optional[
        datamanager_v1.ProductAccount.AccountType
    ] = None,
    linked_account_id: Optional[str] = None,
    key_space: Optional[datamanager_v1.MobileIdInfo.KeySpace] = None,
    app_id: Optional[str] = None,
) -> None:
    """Runs the sample.

    Args:
     operating_account_type: the account type of the operating account.
     operating_account_id: the ID of the operating account.
     validate_only: whether to enable validate_only on the request.
     upload_key_type: the upload key type of the user list.
     login_account_type: the account type of the login account.
     login_account_id: the ID of the login account.
     linked_account_type: the account type of the linked account.
     linked_account_id: the ID of the linked account.
     key_space: the key space of mobile IDs. Required when upload_key_type is
       MOBILE_ID.
     app_id: the mobile application ID. Required when upload_key_type is
       MOBILE_ID.
    """

    # Validates parameters.
    if bool(login_account_type) != bool(login_account_id):
        raise ValueError(
            "Must specify either both or neither of login account type and "
            "login account ID"
        )
    if bool(linked_account_type) != bool(linked_account_id):
        raise ValueError(
            "Must specify either both or neither of linked account type and "
            "linked account ID"
        )
    if (
        upload_key_type
        == datamanager_v1.IngestedUserListInfo.UploadKeyType.MOBILE_ID
    ):
        if not key_space or not app_id:
            raise ValueError(
                "Must specify both key_space and app_id when upload_key_type "
                "is MOBILE_ID"
            )
    elif key_space or app_id:
        raise ValueError(
            "key_space and app_id are only supported when upload_key_type is "
            "MOBILE_ID"
        )

    # Optional: Sets headers to specify the login-account and linked-account if
    # your credentials aren't for a user in the operating account (for example,
    # when authenticating through a Google Ads manager account or Data Partner
    # account).
    headers = []

    if login_account_id:
        headers.append(
            (
                "login-account",
                f"accountTypes/{login_account_type.name}/accounts/{login_account_id}",
            )
        )

    if linked_account_id:
        headers.append(
            (
                "linked-account",
                f"accountTypes/{linked_account_type.name}/accounts/{linked_account_id}",
            )
        )

    # Configures the Customer Match user list.
    # For more details on constructing a UserList, see:
    # https://developers.google.com/data-manager/api/devguides/audiences/google-ads/customer-match/create-audience
    ingested_user_list_info = datamanager_v1.IngestedUserListInfo(
        # Sets to a list containing a single UploadKeyType.
        upload_key_types=[upload_key_type],
    )

    if (
        upload_key_type
        == datamanager_v1.IngestedUserListInfo.UploadKeyType.CONTACT_ID
    ):
        # Use CONTACT_ID for hashed email addresses, phone numbers, or physical
        # addresses.
        ingested_user_list_info.contact_id_info = datamanager_v1.ContactIdInfo(
            data_source_type=(
                datamanager_v1.DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY
            )
        )
    elif (
        upload_key_type
        == datamanager_v1.IngestedUserListInfo.UploadKeyType.USER_ID
    ):
        # Use USER_ID for first-party user IDs assigned to logged-in users.
        ingested_user_list_info.user_id_info = datamanager_v1.UserIdInfo(
            data_source_type=(
                datamanager_v1.DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY
            )
        )
    elif (
        upload_key_type
        == datamanager_v1.IngestedUserListInfo.UploadKeyType.MOBILE_ID
    ):
        # Use MOBILE_ID for mobile advertising IDs.
        ingested_user_list_info.mobile_id_info = datamanager_v1.MobileIdInfo(
            data_source_type=(
                datamanager_v1.DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY
            ),
            key_space=key_space,
            app_id=app_id,
        )
    else:
        raise ValueError(f"Unsupported upload key type: {upload_key_type}")

    user_list = datamanager_v1.UserList(
        # Required. The display name must be unique among all lists owned by the
        # account.
        display_name=(
            f"Customer Match ({upload_key_type.name}) - "
            f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
        ),
        description=f"Customer Match audience for {upload_key_type.name}",
        # Optional. The duration an audience member remains in the list after
        # being added. Must correspond to whole days (defaults to maximum if
        # not set).
        membership_duration=datetime.timedelta(days=30),
        # Required for a Customer Match audience.
        ingested_user_list_info=ingested_user_list_info,
    )

    # Creates a client for UserListService.
    user_list_client = datamanager_v1.UserListServiceClient()

    # Builds the request targeting the parent operating account.
    request = datamanager_v1.CreateUserListRequest(
        parent=f"accountTypes/{operating_account_type.name}/accounts/{operating_account_id}",
        user_list=user_list,
        # Sets validate_only. If true, then the Data Manager API only
        # validates the request but doesn't apply changes.
        validate_only=validate_only,
    )

    # Sends the request with any configured header metadata.
    response: datamanager_v1.UserList = user_list_client.create_user_list(
        request=request,
        metadata=headers,
    )

    # Logs the created user list. When validate_only is false, response.id
    # contains the created user list ID, which you pass as the
    # product_destination_id in subsequent ingest or remove requests.
    _logger.info("Response:\n%s", response)
```

<br />

## Send the request

Send the request and, if required, include
[request headers](https://developers.google.com/data-manager/api/devguides/concepts/destinations.md.txt#when-to-use) such
as `login-account` or `linked-account`.

If the request succeeds, the response contains the created
[`UserList`](https://developers.google.com/data-manager/api/reference/rest/v1/accountTypes.accounts.userLists.md.txt#UserList) with the
output-only `id` and `name` fields populated.

> [!IMPORTANT]
> **Key Point:** Record the `id` returned on the created `UserList`. When you upload or remove audience members in an [`ingest`, `remove`, or `removeAll` request](https://developers.google.com/data-manager/api/reference/rest/v1/audienceMembers.md.txt), pass this `id` as the `productDestinationId` in the request's [`Destination`](https://developers.google.com/data-manager/api/reference/rest/v1/Destination.md.txt).

If the request fails,
[inspect the errors](https://developers.google.com/data-manager/api/devguides/concepts/understand-errors.md.txt) to determine the
cause of the failure, update the request and headers to fix any issues, and then
send the updated request and headers.