Skip to content

Commit 9f35643

Browse files
Merge pull request #44 from googleads/create-user-list
feat: add sample for creating a customer match user list
2 parents b26c659 + a4d23fc commit 9f35643

1 file changed

Lines changed: 287 additions & 0 deletions

File tree

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
#!/usr/bin/env python
2+
# Copyright 2026 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""Sample of creating a user list in an operating account."""
16+
17+
import argparse
18+
import datetime
19+
import logging
20+
from typing import Optional
21+
22+
from google.ads import datamanager_v1
23+
24+
_logger = logging.getLogger(__name__)
25+
26+
27+
# [START create-user-list]
28+
def main(
29+
operating_account_type: datamanager_v1.ProductAccount.AccountType,
30+
operating_account_id: str,
31+
validate_only: bool,
32+
upload_key_type: datamanager_v1.IngestedUserListInfo.UploadKeyType = (
33+
datamanager_v1.IngestedUserListInfo.UploadKeyType.CONTACT_ID
34+
),
35+
login_account_type: Optional[
36+
datamanager_v1.ProductAccount.AccountType
37+
] = None,
38+
login_account_id: Optional[str] = None,
39+
linked_account_type: Optional[
40+
datamanager_v1.ProductAccount.AccountType
41+
] = None,
42+
linked_account_id: Optional[str] = None,
43+
key_space: Optional[datamanager_v1.MobileIdInfo.KeySpace] = None,
44+
app_id: Optional[str] = None,
45+
) -> None:
46+
"""Runs the sample.
47+
48+
Args:
49+
operating_account_type: the account type of the operating account.
50+
operating_account_id: the ID of the operating account.
51+
validate_only: whether to enable validate_only on the request.
52+
upload_key_type: the upload key type of the user list.
53+
login_account_type: the account type of the login account.
54+
login_account_id: the ID of the login account.
55+
linked_account_type: the account type of the linked account.
56+
linked_account_id: the ID of the linked account.
57+
key_space: the key space of mobile IDs. Required when upload_key_type is
58+
MOBILE_ID.
59+
app_id: the mobile application ID. Required when upload_key_type is
60+
MOBILE_ID.
61+
"""
62+
63+
# Validates parameters.
64+
if bool(login_account_type) != bool(login_account_id):
65+
raise ValueError(
66+
"Must specify either both or neither of login account type and "
67+
"login account ID"
68+
)
69+
if bool(linked_account_type) != bool(linked_account_id):
70+
raise ValueError(
71+
"Must specify either both or neither of linked account type and "
72+
"linked account ID"
73+
)
74+
if (
75+
upload_key_type
76+
== datamanager_v1.IngestedUserListInfo.UploadKeyType.MOBILE_ID
77+
):
78+
if not key_space or not app_id:
79+
raise ValueError(
80+
"Must specify both key_space and app_id when upload_key_type "
81+
"is MOBILE_ID"
82+
)
83+
elif key_space or app_id:
84+
raise ValueError(
85+
"key_space and app_id are only supported when upload_key_type is "
86+
"MOBILE_ID"
87+
)
88+
89+
# Optional: Sets headers to specify the login-account and linked-account if
90+
# your credentials aren't for a user in the operating account (for example,
91+
# when authenticating through a Google Ads manager account or Data Partner
92+
# account).
93+
headers = []
94+
95+
if login_account_id:
96+
headers.append(
97+
(
98+
"login-account",
99+
f"accountTypes/{login_account_type}/accounts/{login_account_id}",
100+
)
101+
)
102+
103+
if linked_account_id:
104+
headers.append(
105+
(
106+
"linked-account",
107+
f"accountTypes/{linked_account_type}/accounts/{linked_account_id}",
108+
)
109+
)
110+
111+
# Configures the Customer Match user list.
112+
# For more details on constructing a UserList, see:
113+
# https://developers.google.com/data-manager/api/devguides/audiences/google-ads/customer-match/create-audience
114+
ingested_user_list_info = datamanager_v1.IngestedUserListInfo(
115+
# Sets to a list containing a single UploadKeyType.
116+
upload_key_types=[upload_key_type],
117+
)
118+
119+
if (
120+
upload_key_type
121+
== datamanager_v1.IngestedUserListInfo.UploadKeyType.CONTACT_ID
122+
):
123+
# Use CONTACT_ID for hashed email addresses, phone numbers, or physical
124+
# addresses.
125+
ingested_user_list_info.contact_id_info = datamanager_v1.ContactIdInfo(
126+
data_source_type=(
127+
datamanager_v1.DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY
128+
)
129+
)
130+
elif (
131+
upload_key_type
132+
== datamanager_v1.IngestedUserListInfo.UploadKeyType.USER_ID
133+
):
134+
# Use USER_ID for first-party user IDs assigned to logged-in users.
135+
ingested_user_list_info.user_id_info = datamanager_v1.UserIdInfo(
136+
data_source_type=(
137+
datamanager_v1.DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY
138+
)
139+
)
140+
elif (
141+
upload_key_type
142+
== datamanager_v1.IngestedUserListInfo.UploadKeyType.MOBILE_ID
143+
):
144+
# Use MOBILE_ID for mobile advertising IDs.
145+
ingested_user_list_info.mobile_id_info = datamanager_v1.MobileIdInfo(
146+
data_source_type=(
147+
datamanager_v1.DataSourceType.DATA_SOURCE_TYPE_FIRST_PARTY
148+
),
149+
key_space=key_space,
150+
app_id=app_id,
151+
)
152+
else:
153+
raise ValueError(f"Unsupported upload key type: {upload_key_type}")
154+
155+
user_list = datamanager_v1.UserList(
156+
# Required. The display name must be unique among all lists owned by the
157+
# account.
158+
display_name=(
159+
f"Customer Match ({upload_key_type.name}) - "
160+
f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
161+
),
162+
description=f"Customer Match audience for {upload_key_type.name}",
163+
# Optional. The duration an audience member remains in the list after
164+
# being added. Must correspond to whole days (defaults to maximum if
165+
# not set).
166+
membership_duration=datetime.timedelta(days=30),
167+
# Required for a Customer Match audience.
168+
ingested_user_list_info=ingested_user_list_info,
169+
)
170+
171+
# Creates a client for UserListService.
172+
user_list_client = datamanager_v1.UserListServiceClient()
173+
174+
# Builds the request targeting the parent operating account.
175+
request = datamanager_v1.CreateUserListRequest(
176+
parent=f"accountTypes/{operating_account_type}/accounts/{operating_account_id}",
177+
user_list=user_list,
178+
# Sets validate_only. If true, then the Data Manager API only
179+
# validates the request but doesn't apply changes.
180+
validate_only=validate_only,
181+
)
182+
183+
# Sends the request with any configured header metadata.
184+
response: datamanager_v1.UserList = user_list_client.create_user_list(
185+
request=request,
186+
metadata=headers,
187+
)
188+
189+
# Logs the created user list. When validate_only is false, response.id
190+
# contains the created user list ID, which you pass as the
191+
# product_destination_id in subsequent ingest or remove requests.
192+
_logger.info("Response:\n%s", response)
193+
# [END create-user-list]
194+
195+
196+
if __name__ == "__main__":
197+
# Configures logging.
198+
logging.basicConfig(level=logging.INFO)
199+
200+
parser = argparse.ArgumentParser(
201+
description="Creates a user list in an operating account.",
202+
fromfile_prefix_chars="@",
203+
)
204+
# The following argument(s) should be provided to run the example.
205+
parser.add_argument(
206+
"--operating_account_type",
207+
type=str,
208+
required=True,
209+
help="The account type of the operating account.",
210+
)
211+
parser.add_argument(
212+
"--operating_account_id",
213+
type=str,
214+
required=True,
215+
help="The ID of the operating account.",
216+
)
217+
parser.add_argument(
218+
"--upload_key_type",
219+
choices=["CONTACT_ID", "USER_ID", "MOBILE_ID"],
220+
default="CONTACT_ID",
221+
help="The upload key type of the user list. Defaults to 'CONTACT_ID'.",
222+
)
223+
parser.add_argument(
224+
"--login_account_type",
225+
type=str,
226+
required=False,
227+
help="The account type of the login account.",
228+
)
229+
parser.add_argument(
230+
"--login_account_id",
231+
type=str,
232+
required=False,
233+
help="The ID of the login account.",
234+
)
235+
parser.add_argument(
236+
"--linked_account_type",
237+
type=str,
238+
required=False,
239+
help="The account type of the linked account.",
240+
)
241+
parser.add_argument(
242+
"--linked_account_id",
243+
type=str,
244+
required=False,
245+
help="The ID of the linked account.",
246+
)
247+
parser.add_argument(
248+
"--key_space",
249+
choices=["ANDROID", "IOS"],
250+
required=False,
251+
help="The key space of mobile IDs ('ANDROID' or 'IOS'). Required when "
252+
"--upload_key_type is 'MOBILE_ID'.",
253+
)
254+
parser.add_argument(
255+
"--app_id",
256+
type=str,
257+
required=False,
258+
help="A string that uniquely identifies a mobile application from "
259+
"which the data was collected (for example, 'com.labpixies.flood' for "
260+
"Android or '476943146' for iOS). Required when --upload_key_type is "
261+
"'MOBILE_ID'.",
262+
)
263+
parser.add_argument(
264+
"--validate_only",
265+
choices=["true", "false"],
266+
default="true",
267+
help="Whether to enable validate_only on the request. Must be 'true' "
268+
"or 'false'. Defaults to 'true'.",
269+
)
270+
args = parser.parse_args()
271+
272+
main(
273+
args.operating_account_type,
274+
args.operating_account_id,
275+
args.validate_only == "true",
276+
datamanager_v1.IngestedUserListInfo.UploadKeyType[args.upload_key_type],
277+
args.login_account_type,
278+
args.login_account_id,
279+
args.linked_account_type,
280+
args.linked_account_id,
281+
(
282+
datamanager_v1.MobileIdInfo.KeySpace[args.key_space]
283+
if args.key_space
284+
else None
285+
),
286+
args.app_id,
287+
)

0 commit comments

Comments
 (0)